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-Treiber1 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', " + Date.valueOf("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.
1) In früheren Versionen des Artikels
konnte die Datumsangabe im ersten Statement noch als
einfacher String angegeben werden. Unter jetziger
Verwendung des Treibers sqlite-jdbc-3.40.1.0.jar
ist dies nicht mehr möglich. Der String muss nun wie im
Prepared Statement explizit durch Date.valueOf()
gecastet werden.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.