smart | Webentwicklung
Alles rund um HTML5, PHP, WordPress & Co.

Einführung in die HTML5 Indexed Database API

21. Mai 2013
Stephan
Einführung in die HTML5 Indexed Database API (IndexedDB API)

Nachdem ich bereits die HTML5 WebStorage API zum clientseitigen Speichern von Daten vorgestellt habe, möchte ich euch dieses Mal die Indexed Database API vorstellen.

Diese ist im Gegensatz zur WebStorage API zum Speichern von komplex strukturierten Daten ausgelegt und so können neben einfachen Datentypen auch komplexe Objekte in einer Datenbank gespeichert werden.

Da die IndexedDB API ziemlich komplex ist, dient dieser Artikel wirklich nur als grobe Einführung.

Grundlegende Konzepte & Merkmale der IndexedDB API

Um überhaupt die Funktionsweise der IndexedDB API zu verstehen, habe ich euch vorab einmal die wesentlichen Konzepte bzw. Merkmale der IndexedDB API aufgeführt:

  • Die Daten werden in Form von Schlüssel-Wert-Paaren gespeichert. Werte können einfache Datentypen aber auch komplex strukturierte Objekte sein.
  • Die Datenbank beruht auf einem transaktionalen Datenbankmodell. Das bedeutet, dass jede auszuführende Aktion bzw. Datenbankoperation im Kontext einer Transaktion stattfindet.
  • Bei der Datenbank handelt es sich um eine indexbasierte sowie objektorientierte Datenbank. Demnach gibt es keine Tabellen, Zeilen oder Spalten, wie bei relationalen Datenbanken üblich und es wird auch keine strukturierte Abfragesprache (SQL) unterstützt.
  • Datensätze können zum einen direkt über ihren Schlüssel ausgelesen werden. Zum anderen können mehrere Datensätze mithilfe von speziellen Indizes gefiltert sowie ausgewählt und mit einem Cursor durchlaufen werden.
  • Genauso wie die WebStorage API unterliegt auch die IndexedDB API der Same Origin Policy.

Eine noch ausführlichere Auflistung sowie Beschreibung der grundlegenden Konzepte findet ihr hier: Basic Concepts Behind IndexedDB

Für die IndexedDB API gibt es laut Spezifikation sowohl eine synchrone als auch asynchrone API-Variante. Erstere ist für den Einsatz innerhalb von Worker-Threads gedacht, derzeit aber noch in keinem Webbrowser verfügbar. Aus diesem Grund beziehe ich mich im Weiteren Verlauf des Artikels nur auf die asynchrone Variante.

Begriffe im Kontext der IndexedDB API

Kommen wir nun zur Erläuterung der wichtigsten Begrifflichkeiten, die ihr im Rahmen der IndexedDB API kennen solltet:

  • Datenbank (database): Besteht aus einem Namen und einer Versionsnummer und enthält einen oder mehrere Objektspeicher.
  • Objektspeicher (object store): Besteht aus einem Namen, der ihn innerhalb einer Datenbank eindeutig identifiziert und enthält die eigentlichen Datensätze. Optional kann ein Objektspeicher über einen Schlüsselgenerator und Schlüsselbereich verfügen.
  • Datensatz (record): Ein Schlüssel mit dazugehörigem Wert.
  • Schlüssel (key): Schlüssel eines Datensatzes unter dem ein bestimmter Wert abrufbar ist. Erlaubte Datentypen sind DOMString, Date, float und Array.
  • Wert (value): Der eigentliche Wert eines Datensatzes, der alle Datentypen unterstützt, die auch vom Structered Clone Algorithm unterstützt werden.
  • Schlüsselgenerator (key generator): Mechanismus zum automatischen Generieren von Schlüsseln.
  • Schlüsselpfad (key path): Ein DOMString der angibt, wie der Schlüssel auf Basis einer Eigenschaft des zu speichernden Objekts ermittelt wird.
  • Index (index): Spezieller Objektspeicher zum Suchen bzw. Auswählen von Datensätzen in einem anderen, dem referenzierten Objektspeicher. Der Index wird automatisch bei jeder Änderung des referenzierten Objektspeichers aktualisiert. Ein Index besteht genauso wie ein normaler Objektspeicher aus Schlüssel-Wert-Paaren. Hierbei entspricht der Schlüssel einem Wert oder einer Kombination von Werten und der Wert einem Schlüssel aus dem referenzierten Objektspeicher. Dadurch lassen sich Datensätze nicht nur über ihren Schlüssels auslesen, sondern auch anhand bestimmter Werte bzw. Kombination von Werten.
  • Transaktion (transaction): Eine atomare Folge von auszuführenden Datenbankoperationen. Enthält eine Liste von Datenbankanfragen und hat einen bestimmten Zugriffsmodus und Geltungsbereich. Transaktionen mit gleichem Gültigkeitsbereich können nur dann zur gleichen Zeit ausgeführt werden, wenn es sich beim Zugriffsmodus um readyonly handelt.
  • Anfrage (request): Repräsentiert genau eine lesende oder schreibende Datenbankoperation.
  • Geltungsbereich (scope): Referenziert die Objektspeicher mit denen im Rahmen einer Transaktion interagiert wird.
  • Zugriffsmodus (mode): Entscheidet wie beim konkurrierenden Zugriff auf Objektspeicher innerhalb von Transaktionen verfahren wird. Erlaubte Werte sind readyonly, readwrite und versionchange.
  • Cursor (cursor): Mechanismus zum Iterieren über Datensätze eines Objektspeichers bzw. Index basierend auf einem angegebenen Schlüsselbereich.
  • Schlüsselbereich (key range): Definiert einen Bereich von Schlüsseln zum Filtern von Cursor-Ergebnissen.

Etwas ausführlicher findet ihr das ganze wieder im Artikel Basic Concepts Behind IndexedDB und in der W3C-Spezifikation selbst.

Schlüsselzuordnung

Die Zuweisung eines Schlüssel erfolgt entweder mittels dem erwähnten Schlüsselgenerator, Schlüsselbereich oder der separaten Angabe eines Schlüssels. Die möglichen Kombinationsvarianten findet ihr im Artikel Using IndexedDB.

Die IndexedDB API – Beispiel

Im Folgenden wollen wir einen kleinen Einstieg in die Implementierung der IndexedDB API wagen. Es sei noch mal erwähnt, dass wir es mit einer asynchronen API zu tun haben, und somit eine Callback- bzw. Event-basierte Programmierung anwenden müssen.

Datenbank erstellen & öffnen
Zunächst einmal müssen wir eine Datenbank erstellen und öffnen. Erstgenanntes wird automatisch gemacht, sobald wir versuchen eine Datenbank zu öffnen, die noch nicht existiert:

var openDbRequest = window.indexedDb.open('myDatabase');

Jede Datenbank hat eine Version, die beim Öffnen als zweiter Parameter mit angegeben werden kann.

Als nächstes implementieren wir zwei Event-Handler:

var db;

openDbRequest.onsuccess = function()
{
    db = this.result;

    db.onerror = function(event)
    {
        console.log(event);
    };

    storeSomething();
};

openDbRequest.onerror = function(event)
{
    console.log(event);
};

Der onsuccess-Event-Handler wird aufgerufen, sobald die Datenbank erfolgreich erstellt und geöffnet werden konnte. Als result erhalten wir ein Objekt, dass uns auf unsere Datenbank zugreifen lässt. Entsprechend wird onerror aufgerufen, falls ein Fehler auftritt.

Objektspeicher erstellen
Schlüssel-Wert-Paare werden immer in einem Objektspeicher gespeichert. Deshalb müssen wir nun einen solchen Objektspeicher erstellen. Dazu müssen wir einen weiteren Event-Handler implementieren:

openDbRequest.onupgradeneeded = function(event)
{
    db = event.currentTarget.result;

    db.createObjectStore('myObjectStore', {
        keyPath: 'id',
        autoIncrement: true
    });
};

Der onupgradeneeded-Event-Handler wird aufgerufen, wenn die angegebene Version der zu öffnenden Datenbank größer als die gespeicherte Version ist bzw. eine Datenbank neu erstellt wurde. Die einzige Möglichkeit Objektspeicher zu erstellen, zu bearbeiten oder zu löschen ist innerhalb dieses Event-Handlers.

In unserem Beispiel erstellen wir nun einen Objektspeicher, der als Schlüssel eine id verwendet, die, falls nicht vorhanden, automatisch generiert wird.

Objekte speichern
Jetzt wollen wir natürlich auch etwas in unsere Datenbank und in unseren Objektspeicher speichern. Beim öffnen der Datenbank hatten wir innerhalb des onsuccess-Event-Handler eine storeSomething-Funktion aufgerufen. Diese wollen wir nun implementieren.

Zuallererst müssen wir eine Transaktion erzeugen:

function storeSomething()
{
    var transaction = db.transaction('myObjectStore', 'readwrite');
}

Diese Transaktion hat als Geltungsbereich unseren erstellen Objektspeicher und als Zugriffmodus readwrite. Auch hier können nun wieder Event-Handler implementiert werden. Für dieses Beispiel verzichte ich jetzt aber mal darauf.

Wir greifen nun über die Transaktion bzw. im Kontext der Transaktion auf unseren Objektspeicher zu und speichern unser Objekt:

function storeSomething()
{
    var transaction = db.transaction('myObjectStore', 'readwrite');

    var objectStore = transaction.objectStore('myObjectStore');

    var request = objectStore.add({
        name: 'Tom Meier',
        age: '29'
    });

    request.onsuccess = function(event)
    {
        console.log(event.target.result);
    };
}

Mittels add fügen wir unser Objekt in den Objektspeichern ein. Wir hätten auch die put-Methode nutzen können, die im Gegensatz zu add auch vorhandene Werte überschreibt. Auch in diesem Fall müssen wir wieder einen Event-Handler implementieren, um feststellen zu können, ob wirklich alles geklappt hat. Wenn dem so ist, wird unser onsuccess-Event-Handler aufgerufen. Mittels event.target.result können wir zudem die generierte ID, unter der unser Objekt gespeichert wurde, abrufen.

Einzelnes Objekt auslesen
Was nutzt uns nun ein gespeichertes Objekt in einer Datenbank, wenn wir es nicht wieder auslesen können? Nicht viel! Also wollen wir nun unser Objekt aus der Datenbank holen. Wenn wir den entsprechenden Schlüssel, in unserem Fall die id kennen, dann ist das recht einfach mittels get zu bewerkstelligen:

var transaction = db.transaction('myObjectStore');

var objectStore = transaction.objectStore('myObjectStore');

var request = objectStore.get(1);

request.onsuccess = function(event)
{
    console.log(event.result.name);
};

Geben wir bei der Erstellung einer Transaktion keinen Zugriffmodus an, ist standardmäßig readonly ausgewählt. Auch in diesen Fall bleibt uns die Implementierung eines Event-Handlers nicht erspart. Beim Aufruf von onsuccess ist unser gespeichertes Objekt über event.result verfügbar.

Wir ihr wisst hatten wir ein Objekt mit einer name– und age-Eigenschaft gespeichert. Beim Speichern des Objekts wurde nun aber die Eigenschaft id hinzugefügt. Ihr könnt nun also die anderen Eigenschaften des Objekts ganz normal ändern und dann mittels put speichern, damit das Objekt in der Datenbank aktualisiert wird.

Mehrere Objekte auslesen
Oftmals kennen wir nicht immer den Schlüssel des Objekts, dass wir aus der Datenbank auslesen möchten bzw. wir möchten mehrere Objekte gleichzeitig auslesen. Hierfür gibt es nun das Prinzip der Cursor:

var transaction = db.transaction('myObjectStore');

var objectStore = transaction.objectStore('myObjectStore');

var cursor = objectStore.openCursor()

cursor.onsuccess = function(event)
{
    var cursor = event.target.result;

    if(cursor)
    {
        console.log(cursor.value.name);
				
        cursor.continue();
    }
    else
    {
        // alle Objekte ausgelesen
    }
};

Wir definieren uns hier also einen Cursor und iteriieren über alle gespeicherten Objekte des angegebenen Objektspeichers. Beispielsweise könntet ihr auch noch einen Schlüsselbereich zum Filtern der Ergebnisse angegeben.

Objekte löschen
Um ein Objekt zu löschen, können wir die delete-Methode einsetzen:

var transaction = db.transaction('myObjectStore', 'readwrite');

var objectStore = transaction.objectStore('myObjectStore');

var request = objectStore.delete(1);

Browser-Unterstützung

Gegenüber der WebStorage API ist die IndexedDB API noch nicht in allen gängigen Webbrowern verfügbar. Eine genaue Übersicht findet ihr wie immer auf caniuse.com.

Innerhalb von HTML5 Web Workern wird die IndexedDB API aktuell (soweit meine Tests ergeben haben) nur von Chrome und dem IE 10 unterstützt.

Weiterführende Informationen

Wie bereits erwähnt, ist die IndexedDB API doch recht umfangreich und lässt sich nicht, wie z.B. die WebStorage API in einem Artikel komplett erklären/beschreiben. Aus diesem Grund verweise ich euch mal auf folgende Quellen, die sich ausführlich mit der IndexedDB API beschäftigen:

Fazit

Mit der IndexedDB API gibt es eine weitere Möglichkeit Daten clientseitig im Webbrowser zu speichern. Allerdings ist die IndexedDB API recht umfangreich und komplex, ist dafür gegenüber der WebStorage API aber auch von der Funktionalität her mächtiger und kann zudem innerhalb von Web Workern eingesetzt werden.

Habt ihr die IndexedDB API schon einmal ausprobiert? Wie sind eure Erfahrungen damit?

Kommentare  
0 Kommentare vorhanden
0 Trackbacks/Pingbacks vorhanden
Du bist herzlich eingeladen auch ein Kommentar zu hinterlassen!
Kommentar schreiben

Vielen Dank für dein Kommentar!