Wie kann in Java eine lokale SQLite-Datenbank genutzt werden?
Um das Beispiel erfolgreich ausführen zu können, muss zunächst der passende Datenbank-Treiber heruntergeladen und in den Classpath des Programms eingebunden werden.
Die Beispielklasse selbst ist als Singleton realisiert. Sie kann so zu einer Klasse zur zentralen Datenbanksteuerung ausgebaut werden, von der nur ein Objekt programmweit existieren sollte.
Gleich zu Beginn werden drei Instanzvariablen deklariert. Die erste
ist mit dem Singleton-Objekt der Klasse initialisiert, die zweite
ein Connection
-Objekt, das später zum
Verbindungsaufbau zur Datenbank genutzt wird und die dritte
enthält den Pfad zur Datenbank-Datei. Er kann innerhalb der
Rechte, die dem Programmnutzer zustehen, frei gewählt werden.
Es
folgt ein statischer Block, in dem der Datenbanktreiber geladen
wird, sowie der leere, hier beim Singleton private
deklarierte Konstruktor.
In der main
-Methode
werden zum Ausführen des Programms nacheinander die beiden
realisierten Methoden aufgerufen: initDBConnection()
baut die Verbindung zur Datenbank auf und schließt sie bei
Programmende, handleDB()
erzeugt einige Einträge
und fragt sie anschließend zu Demonstrationszwecken ab.
Der Reihe nach! Nach einer Sicherheitsabfrage, die
geährleistet, dass eine bereits bestehende Verbindung nicht
erneut aufgebaut wird, findet der Verbindungsaufbau durch die
statische Methode getConnecion()
der Klasse DriverManager
statt. Ihr wird eine URL als Parameter übergeben, dessen
letzter Teil der Pfad zur Datenbankdatei ist.
Wie oben
erwähnt, kann der Pfad im Rahmen der Rechte des Users frei
gewählt werden. Die Datei selbst wird automatisch neu gebildet,
wenn sie nicht bereits existiert. Der gegen Fehler beim
Verbindungsaufbau durch eine Exception abgesicherte Block wird durch
eine Kontrollausgabe des Connecion
-Objectes
abgeschlossen.
In der nächsten Anweisungsfolge wird ein
neuer Thread gebildet, innerhalb dessen die bestehende Verbindung
geschlossen wird. Er bleibt zunächst ungestartet, und wird als
Parameter dem aktuellen Runtime
-Objekt als 'Shutdown-Hook'
übergeben. Dies bedeutet, dass die Viruelle Maschine beim
Herunterfahren diesen Thread startet und somit die
Datenbankverbindung sicher schließt.
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
class DBController {
private static final DBController dbcontroller = new DBController();
private static Connection connection;
private static final String DB_PATH = System.getProperty("user.home") + "/" + "testdb.db";
static {
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
System.err.println("Fehler beim Laden des JDBC-Treibers");
e.printStackTrace();
}
}
private DBController(){
}
public static DBController getInstance(){
return dbcontroller;
}
private void initDBConnection() {
try {
if (connection != null)
return;
System.out.println("Creating Connection to Database...");
connection = DriverManager.getConnection("jdbc:sqlite:" + DB_PATH);
if (!connection.isClosed())
System.out.println("...Connection established");
} catch (SQLException e) {
throw new RuntimeException(e);
}
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
if (!connection.isClosed() && connection != null) {
connection.close();
if (connection.isClosed())
System.out.println("Connection to Database closed");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
});
}
private void handleDB() {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("DROP TABLE IF EXISTS books;");
stmt.executeUpdate("CREATE TABLE books (author, title, publication, pages, price);");
stmt.execute("INSERT INTO books (author, title, publication, pages, price) VALUES ('Paulchen Paule', 'Paul der Penner', '2001-05-06', '1234', '5.67')");
PreparedStatement ps = connection
.prepareStatement("INSERT INTO books VALUES (?, ?, ?, ?, ?);");
ps.setString(1, "Willi Winzig");
ps.setString(2, "Willi's Wille");
ps.setDate(3, Date.valueOf("2011-05-16"));
ps.setInt(4, 432);
ps.setDouble(5, 32.95);
ps.addBatch();
ps.setString(1, "Anton Antonius");
ps.setString(2, "Anton's Alarm");
ps.setDate(3, Date.valueOf("2009-10-01"));
ps.setInt(4, 123);
ps.setDouble(5, 98.76);
ps.addBatch();
connection.setAutoCommit(false);
ps.executeBatch();
connection.setAutoCommit(true);
ResultSet rs = stmt.executeQuery("SELECT * FROM books;");
while (rs.next()) {
System.out.println("Autor = " + rs.getString("author"));
System.out.println("Titel = " + rs.getString("title"));
System.out.println("Erscheinungsdatum = "
+ rs.getDate("publication"));
System.out.println("Seiten = " + rs.getInt("pages"));
System.out.println("Preis = " + rs.getDouble("price"));
}
rs.close();
connection.close();
} catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public static void main(String[] args) {
DBController dbc = DBController.getInstance();
dbc.initDBConnection();
dbc.handleDB();
}
}
Innerhalb der Methode handleDB()
wird als erstes ein Statement
-Objekt
erzeugt. Auf ihm werden drei SQL-Anweisungen ausgeführt, die
nacheinader zunächst eine eventuell existierende Tabelle 'books'
löscht, sie anschließend wieder neu erzeugt und
schließlich in diese Tabelle einen einzelnen Datensatz
einträgt.
Es fällt auf, dass hier zwei verschiedene
Methoden verwandt werden:
executeUpdate()
führt Anfragen aus, die, wie bspw. INSERT, UPDATE oder DELETE, nichts zurückgeben.execute()
ist mehrfach überladen und kann mehrere Ergebnisse liefern. Die Methode gibt einen boolschen Wert zurück dertrue
ist, wenn einResultSet
erzeugt wurde undfalse
, falls entweder gar keine Rückgabe oder die Anzahl der geänderten Datensätze geliefert wurde.
Die folgenden Zeilen zeigen die Ausführung eines prepared
Statements, einer Abfrageform, die besonders gut geeignet ist,
um mehrere Abfragen nacheinander auszuführen.
Das PreparedStatement
-Objekt
wird direkt auf dem Verbindungs-Objekt erzeugt. Ihm wird bei der
Bildung direkt der Abfrage-String übergeben. Hier handelt es
sich um ein konventionelles INSERT-Statement, das jedoch
statt der Werte Fragezeichen als Platzhalter enthält. Sie
werden bei der Ausführung des Statements durch die konkreten
Werte ersetzt. Diese Übergabe erfolgt durch Setter-Methoden,
die für die verschiedenen Datentypen zur Verfügung stehen.
Der erste Parameter dieser Methoden bezeichnet die Spalte, der
zweite den Wert des Eintrags.
Die Methode addBatch()
fügt den erzeugten Auftrag
einem Stapel an Querys hinzu, der schließlich als Gesamtheit
durch executeBatch()
ausgeführt wird. Vorher wird
der Auto-Commit-Modus auf false
gesetzt, um diesen
Anweisungs-Stapel als geschlossene Einheit auszuführen.
Die Abfrage der eingetragenen Datensätze erfolgt mittels einer
weiteren execute...-Methode, executeQuery()
. Sie
liefert ein ResultSet
-Objekt, das in einer Schleife
ausgelesen wird und die Daten mit Hilfe diverser Getter-Methoden
liefert. Die Datenbank-Spaltennamen werden in Form von Strings als
Parameter übergeben.
Den Abschluss bilden die Schließung des ResultSets
und der Datenbank-Verbindung.