In der 11. Jahrgangsstufe sollen die Schüler am bayerischen G8 ein Programmierprojekt in einer objektorientierten Sprache (in der Regel: Java) umsetzen, das mit einer SQL-Datenbank zusammenarbeitet. Die ISB-Handreichung stellt als Beispiel eine Börsensimulation und einen Fahrradroutenplaner zur Verfügung; ich habe schon mal mit Schülern zwei schöne Projekte gemacht und bin mit zwei anderen gescheitert. Dabei spielt die Datenbank für mich keine große Rolle; sie hat absolut nichts mit dem Lehrplan von Q11 oder Q12 zu tun, die Schüler interessieren sich nicht dafür, und für die meisten Projekte braucht man eigentlich keine. Das bisschen, das man meist speichern will, geht auch anders. Aber gut, es soll nun mal so sein.
Java und SQL-Datenbanken in der Schule haben einige Tücken, wie ich nach und nach herausgefunden habe. Mit etwas Basteln funktionierte das Projekt – zumindest in der Schule, aber nicht unbedingt bei den Schülern zu Hause, und nicht unter einem anderen Betriebssysteme, oder auch nur innerhalb der Entwicklungsumgebung und nicht als eigenstäniges Java-Produkt. Ich schreibe hier mal zusammen, was ich herausgefunden habe.
- Wahl des Datenbanksystems: Access, MySQL, SQLite, Base?
- Integrieren des passenden Treibers
- Grundsätzliche Aufbau einer Anfrage: Unterschiede bei der Syntax
- Die Verbindung zur Datenbank: Hauptproblem Speic1herort bei lokalen Dateien
1. Wahl des Datenbanksystems
Es gibt viele Möglichkeiten, mit SQL-Syntax zu arbeiten.
MS Access
Vorteile: Treiber ist bereits vorhanden. Man kann die Datenbank leicht außerhalb von Java bearbeiten.
Nachteile: Läuft nur unter Windows und nur, wenn Access installiert ist.
MySQL-Server
Vorteile: Läuft bei allen Betriebssystemen. Man hat keine Probleme mit dem Speicherort der DB (siehe später).
Nachteile: Man muss den richtigen Treiber finden; man braucht schülerferne Werkzeuge wie phpMyAdmin, um die DB zu bearbeiten; und vor allem: man braucht einen laufenden MySQL-Server. Das geht in der Schule noch, aber zu Hause bei den Schülern schon weniger, und weitergeben an Eltern und Freunde kann man das Programm dann auch nur, wenn man denen die Serverinfrastruktur mitgibt.
Open Office Base
Vorteile: Läuft bei allen Betriebssystemen. Man kann die DB leicht in Base erstellen.
Nachteile: Man muss den richtigen Treiber finden. Vor allem: Java kann nicht unmittelbar mit der .odb-Datei arbeiten. Die ist eigentlich ein gezipptes Paket, das einen Ordner database
enthält. Dieser Ordner enthält die 4 Dateien backup, data, properties
und script
. Die muss man auspacken und umbenennen in zum Beispiel xxx.backup, xxx.data, xxx.properties
und xxx.script
– und dann erst kann Java mit der Datenbank unter dem Namen xxx
im HSQLDB-Format arbeiten.
SQLite
Vorteile: Läuft bei allen Betriebssystemen.
Nachteile: Man muss den richtigen Treiber finden. Es bietet nur eine – für die Schule aber voll ausreichende – Teilmenge von SQL; es gibt keine Fremdschlüssel und keine Kontrolle der Typen – um die Datenintegrität muss man sich selber kümmern. Zum Erstellen und Warten der DB braucht man ein eigenes Programm, davon gibt es aber genug, etwa den SQLite Database Browser.
— Inzwischen neige ich zu SQLite.
2. Integrieren des Treibers
Wenn man sich für ein DBS entschieden hat, braucht Java den Treiber dazu. Den findet man im Web in Form einer .jar-Datei, die man in sein Projekt integrieren muss. (Dafür gibt es verschiedene Möglichkeiten. In BlueJ bindet man die Datei als Bibliothek ein, oder man kopiert sie in einen Ordner namens +libs
im BlueJ-Projektverzeichnis.)
DB-Typ | Treiberbezeichner | Präfix | Treiber zu finden z.B. in |
MS Access | „sun.jdbc.odbc.JdbcOdbcDriver“ | „jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=“ | -integriert- |
MySQL | „com.mysql.jdbc.Driver“ | „jdbc:mysql://“ | mysql-connector-java-5.1.25-bin.jar |
Base | „org.hsqldb.jdbcDriver“ | „jdbc:hsqldb:“ | hsqldb.jar |
SQLite | „org.sqlite.JDBC“ | „jdbc:sqlite:“ | sqlite-jdbc-3.7.2.jar |
Neben dem Treiber selber brauchte man in früheren Java-Versionen dessen Bezeichner, und man braucht immer noch einen Präfix dazu, der beim Einbauen dem Namen der DB vorangestellt wird.
3. Grundsätzliche Aufbau einer Anfrage
Ich habe den Aufbau einer SQL-Anfrag ein Java in 10 Schritte aufgeteilt. Hier sind sie in einer Klasse dazu. Damit das funktioniert, braucht man noch die Datenbankdatei quiz.sqlite
, aber dazu später mehr.
import java.sql.*;
class DBVerbindung {
//Attribute
String treiberbezeichner = "org.sqlite.JDBC";
String praefix = "jdbc:sqlite:";
String urlstring = "quiz.sqlite";
String user = "";
String kennwort = "";
//Anfragestring zu Demo-Zwecken hier ausgelagert
String anfragestring = "SELECT * FROM fragen WHERE antwort1='H2O';";
String spaltenbezeichner = "frage";
void test() {
try {
//1. Treiber laden
Class.forName(treiberbezeichner);
//2. Verbindung herstellen
Connection conn = DriverManager.getConnection(praefix+urlstring, user, kennwort);
//3. Statement erzeugen
Statement stmt = conn.createStatement();
//4. Abfrage durchfuehren und Ergebnismenge erhalten
ResultSet rset = stmt.executeQuery(anfragestring);
//bzw. stmt.executeUpdat(auftragInSQL)
//5. Ergebnismenge verarbeiten (in Schleife, mit getInt/getString)
while (rset.next()) { System.out.println (rset.getString(spaltenbezeichner)); }
//6. Ergebnismenge schliessen
rset.close();
//7. Statement schliessen
stmt.close();
//8. Verbindung schliessen
conn.close();
}
catch (Exception e) {
System.out.println(e);
}
}
}
Den Schritt 1 kann man in neueren Java-Versionen weglassen, da Java den Treiber automatisch lädt, wenn er den Präfix korrekt zuordnen kann. Schritt 2, der Aufbau der Verbindung zur Datenbank, macht am meisten Schwierigkeiten, dazu später. Schritt 4 und 5 sind das Stellen und Verarbeiten der SQL-Abfrage. Hier kann man natürlich optimieren, wenn man sich für geschickte SQL-Abfragen interessiert. Für ein Schulprojekt: Nicht wirklich nötig.
Schwierigkeiten kann die SQL-Syntax im Abfragestring machen. Die unterscheidet sich in Details nämlich, je nachdem, welches System man benutzt:
1. "SELECT * FROM fragen WHERE aw1 = 'H2O';"
2. "SELECT * FROM \"fragen\" WHERE \"aw1\"= 'H2O';"
3. "SELECT * FROM `fragen` WHERE `aw1` = 'H2O';"
4. "SELECT * FROM 'fragen' WHERE 'aw1' = 'H2O';"
Bei MS Access funktionieren die Varianten 1, 2 und 3, bei MySQL 1 und 3, bei Base 2, und Variante 4 funktioniert nie (die mit einfachen Apostrophen um Tabellen- und Spaltenbezeichner).
Auch bei der Auswahl von Spalten aus mehreren Tabellen, wie man sie bei Joins braucht, gibt es kleine Syntaxunterschiede.
4. Die Verbindung zur Datenbank
Die Hauptschwierigkeit, habe ich gesagt, ist die Verbindung zur Datenbank. Handelt es sich um eine DB von einem DB-Server, ist das kein Problem. Man schreibt:
//2. Verbindung herstellen
Connection conn = DriverManager.getConnection (
praefix+"127.0.0.1/MeineDB",
user,
kennwort
);
Und das war’s. Statt IP-Adresse kann man auch gleich Domain/TLD angeben.
Wenn die DB in einer Datei gespeichert ist, ist es schwieriger. Es funktioniert perfekt, den Pfad zur Datei absolut anzugeben:
//2. Verbindung herstellen
Connection conn = DriverManager.getConnection (
praefix+"C:\Eigene Dateien\MeinProjekt\BlueJ-Projektordner\datenbank.db",
user,
kennwort
);
Nur ist das ja sinnlos, weil ich das Projekt ja anderen Leuten geben möchte, und die haben eine ganz andere Ordnerstruktur. Also brauche ich einen relativen Pfad und keinen absoluten. Es funktioniert zum Beispiel, wenn ich die DB-Datei in mein BlueJ-Projekt platziere:
//2. Verbindung herstellen
Connection conn = DriverManager.getConnection (
praefix+"datenbank.db",
user,
kennwort
);
Das heißt, es funktioniert zumindest, solange ich mit BlueJ oder Eclipse arbeite. Ich will aber aus dem Projekt irgendwann mal ein .jar-Archiv machen, damit ich es weitergeben kann, und dann kommt plötzlich keine Datenbankverbindung mehr zustande. Der Grund: der relative Pfad in meinem Code ist immer relativ zu dem Verzeichnis, in dem Java gerade ausgeführt wird. Und das ist nicht unbedingt dasjenige, das man erwartet.
Wenn ich durch Doppelklick oder auf der Kommandozeile mein Java-Archiv starte:
C:\Eigene Dateien\MeinProjekt> java -jar projekt.jar
dann ist das aktuelle Verzeichnis C:\Eigene Dateien\MeinProjekt, und da sucht Java nach der Datenbankdatei. Schlimmer noch, ich kann auf der Kommandozeile mein Programm ja auch so starten:
C:\Eigene Dateien> java -jar MeinProjekt\projekt.jar
dann ist das aktuelle Verzeichnis wieder ein anderes, nämlich C:\Eigene Dateien\.
Kurz gesagt: man kann nicht wissen, was das aktuelle Verzeichnis ist. (Außerdem will ich meine Datenbankdatei ohnehin nicht in einem .jar-Archiv haben, sondern außerhalb davon. Auf Material in dem Archiv kann, während es ausgeführt wird, nämlich nur lesend zugegriffen werden, geschrieben werden kann dann nichts in die Datei.)
Das gilt alles genauso für Hintergrundgrafiken. Wenn ich schreibe:
ImageIcon icon = new ImageIcon("bild.jpg");
dann sucht Java im aktuellen Verzeichnis nach der Datei „bild.jpg“ – aber das aktuelle Verzeichnis muss nicht das sein, das man erwartet. Also lässt man sich zuerst die URL der Grafikdatei geben und übergibt die dann dem ImageIcon-Konstruktor:
java.net.URL imageURL = getClass().getResource("bild.jpg");
try {
ImageIcon icon = new ImageIcon(imageURL);
}
Das geht.
Und so ähnlich muss man es auch mit Datenbankdateien machen, also den absoluten Pfad zur Laufzeit ermitteln. Nur ist das eigentlich nicht vorgesehen. Bei stackoverflow.com bin ich aber auf eine Lösung gestoßen. Mit diesem Code ermittelt man zur Laufzeit den Pfad zu dem Verzeichnis über dem Verzeichnis, in dem Java gerade ausgeführt wird:
private String pfadZumOberverzeichnis() {
java.security.ProtectionDomain pd = getClass().getProtectionDomain();
if ( pd == null ) return null;
java.security.CodeSource cs = pd.getCodeSource();
if ( cs == null ) return null;
java.net.URL url = cs.getLocation();
if ( url == null ) return null;
java.io.File f = new java.io.File( url.getFile() );
if (f == null) return null;
String pfad = "";
try {
pfad = f.getParentFile().getAbsolutePath();
pfad = java.net.URLDecoder.decode(pfad, "utf-8");
} catch (Exception e) { }
return pfad + "/";
}
Und in dieses Verzeichnis außerhalb von und parallel zum BlueJ-Ordner bzw. außerhalb von und parallel zum .jar-Archiv steckt man die Datenbankdatei. Dann stellt man die Verbindung so her:
//2. Verbindung herstellen
Connection conn = DriverManager.getConnection(
protokoll+pfadZumOberverzeichnis()+"datenbank.db",
user,
kennwort
);
Inzwischen klappt das mit meinen Datenbanken auf Linux und Windows, als Archiv oder Projekt in der Entwicklungsphase.
Schreibe einen Kommentar