Advertisement
  1. Code
  2. Android SDK

Android-Architekturkomponenten: die Room-Persistenzbibliothek

by
Read Time:16 minsLanguages:
This post is part of a series called Android Architecture Components.
Android Architecture Components: LiveData

German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)

In diesem letzten Artikel der Serie über Android-Architekturkomponenten untersuchen wir die Room-Persistenzbibliothek, eine hervorragende neue Ressource, die die Arbeit mit Datenbanken in Android erheblich erleichtert. Es bietet eine Abstraktionsschicht über SQLite, zur Kompilierzeit geprüfte SQL-Abfragen und auch asynchrone und beobachtbare Abfragen. Room bringt Datenbankoperationen auf Android auf eine andere Ebene.

Da dies der vierte Teil der Reihe ist, gehe ich davon aus, dass Sie mit den Konzepten und Komponenten des Architekturpakets wie LiveData und LiveModel vertraut sind. Wenn Sie jedoch keinen der letzten drei Artikel gelesen haben, können Sie trotzdem folgen. Wenn Sie jedoch nicht viel über diese Komponenten wissen, nehmen Sie sich etwas Zeit, um die Serie zu lesen – Sie können es genießen.

1. Die Room-Komponente

Wie bereits erwähnt, ist Room kein neues Datenbanksystem. Es ist eine abstrakte Schicht, die die von Android übernommene Standard-SQLite-Datenbank umschließt. Room fügt SQLite jedoch so viele Funktionen hinzu, dass es fast unmöglich ist, sie zu erkennen. Room vereinfacht alle datenbankbezogenen Operationen und macht sie auch viel leistungsfähiger, da es die Möglichkeit bietet, Observables und zur Kompilierzeit geprüfte SQL-Abfragen zurückzugeben.

Room besteht aus drei Hauptkomponenten: der Datenbank, dem DAO (Data Access Objects) und der Entität. Jede Komponente hat ihre Verantwortung und alle müssen implementiert werden, damit das System funktioniert. Glücklicherweise ist eine solche Implementierung recht einfach. Dank der bereitgestellten Annotationen und abstrakten Klassen wird der Boilerplate zur Implementierung von Room auf ein Minimum reduziert.

  • Entity ist die Klasse, die in der Datenbank gespeichert wird. Für jede mit @Entity annotierte Klasse wird eine exklusive Datenbanktabelle erstellt.
  • Das DAO ist die mit @Dao annotierte Schnittstelle, die den Zugriff auf Objekte in der Datenbank und deren Tabellen vermittelt. Es gibt vier spezifische Anmerkungen für die grundlegenden DAO-Operationen: @Insert, @Update, @Delete und @Query.
  • Die Database-Komponente ist eine mit @Database annotierte abstrakte Klasse, die RoomDatabase erweitert. Die Klasse definiert die Liste der Entitäten und ihre DAOs.

2. Einrichten der Umgebung

Um Room zu verwenden, fügen Sie dem App-Modul in Gradle die folgenden Abhängigkeiten hinzu:

Wenn Sie Kotlin verwenden, müssen Sie das kapt-Plugin anwenden und eine weitere Abhängigkeit hinzufügen.

3. Entität, die Datenbanktabelle

Eine Entität stellt das Objekt dar, das in der Datenbank gespeichert wird. Jede Entity-Klasse erstellt eine neue Datenbanktabelle, wobei jedes Feld eine Spalte darstellt. Anmerkungen werden zum Konfigurieren von Entitäten verwendet, und ihr Erstellungsprozess ist wirklich einfach. Beachten Sie, wie einfach es ist, eine Entity mit Kotlin-Datenklassen einzurichten.

Sobald eine Klasse mit @Entity annotiert ist, erstellt die Room-Bibliothek automatisch eine Tabelle mit den Klassenfeldern als Spalten. Wenn Sie ein Feld ignorieren müssen, kommentieren Sie es einfach mit @Ignore. Jede Entity muss außerdem einen @PrimaryKey definieren.

Tabelle und Spalten

Room verwendet die Klasse und ihre Feldnamen, um automatisch eine Tabelle zu erstellen. Sie können jedoch die generierte Tabelle personalisieren. Um einen Namen für die Tabelle zu definieren, verwenden Sie die Option tableName in der Annotation @Entity. Fügen Sie zum Bearbeiten des Spaltennamens eine Annotation @ColumnInfo mit der Option name in das Feld ein. Beachten Sie, dass bei den Tabellen- und Spaltennamen die Groß-/Kleinschreibung beachtet werden muss.

Indizes und Eindeutigkeitsbeschränkungen

Es gibt einige nützliche SQLite-Einschränkungen, die Room uns ermöglicht, auf unseren Entitäten leicht zu implementieren. Um die Suchabfragen zu beschleunigen, können Sie SQLite-Indizes in den Feldern erstellen, die für solche Abfragen relevanter sind. Indizes machen Suchanfragen viel schneller; Sie verlangsamen jedoch auch das Einfügen, Löschen und Aktualisieren von Abfragen, sodass Sie sie sorgfältig verwenden müssen. Sehen Sie sich die SQLite-Dokumentation an, um sie besser zu verstehen.

Es gibt zwei verschiedene Möglichkeiten, Indizes in Room zu erstellen. Sie können einfach die ColumnInfo-Eigenschaft index auf true setzen, damit Room die Indizes für Sie festlegen kann.

Wenn Sie mehr Kontrolle benötigen, verwenden Sie die indices-Eigenschaft der @Entity-Annotation und listen Sie die Namen der Felder auf, die den Index in der value-Eigenschaft bilden müssen. Beachten Sie, dass die Reihenfolge der Elemente im value wichtig ist, da sie die Sortierung der Indextabelle definiert.

Eine weitere nützliche SQLite-Einschränkung ist unique, die verbietet, dass das markierte Feld doppelte Werte enthält. Leider stellt Room in Version 1.0.0 diese Eigenschaft nicht direkt im Entitätsfeld bereit. Sie können jedoch einen Index erstellen und ihn eindeutig machen, um ein ähnliches Ergebnis zu erzielen.

Andere Einschränkungen wie NOT NULL, DEFAULT und CHECK sind in Room nicht vorhanden (zumindest bis jetzt in Version 1.0.0), aber Sie können Ihre eigene Logik für die Entity erstellen, um ähnliche Ergebnisse zu erzielen. Um Nullwerte für Kotlin-Entitäten zu vermeiden, entfernen Sie einfach das ? am Ende des Variablentyps oder fügen Sie in Java die Annotation @NonNull hinzu.

Beziehung zwischen Objekten

Im Gegensatz zu den meisten objektrelationalen Mapping-Bibliotheken erlaubt Room nicht, dass eine Entität direkt auf eine andere verweist. Dies bedeutet, dass Sie, wenn Sie eine Entität namens NotePad und eine namens Note haben, keine Collection von Note innerhalb des NotePad erstellen können, wie dies bei vielen ähnlichen Bibliotheken der Fall wäre. Auf den ersten Blick mag diese Einschränkung ärgerlich erscheinen, aber es war eine Designentscheidung, die Room-Bibliothek an die Architekturbeschränkungen von Android anzupassen. Um diese Entscheidung besser zu verstehen, werfen Sie einen Blick auf die Erklärung von Android für ihren Ansatz.

Obwohl die Objektbeziehung von Room begrenzt ist, existiert sie immer noch. Mit Fremdschlüsseln können übergeordnete und untergeordnete Objekte referenziert und deren Änderungen kaskadiert werden. Beachten Sie, dass es auch empfohlen wird, einen Index für das untergeordnete Objekt zu erstellen, um vollständige Tabellenscans zu vermeiden, wenn das übergeordnete Objekt geändert wird.

Einbetten von Objekten

Es ist möglich, Objekte mit der Annotation @Embedded in Entitäten einzubetten. Sobald ein Objekt eingebettet ist, werden alle seine Felder als Spalten in der Tabelle der Entität hinzugefügt, wobei die Feldnamen des eingebetteten Objekts als Spaltennamen verwendet werden. Betrachten Sie den folgenden Code.

Im obigen Code ist die Location-Klasse in die Note-Entität eingebettet. Die Tabelle der Entität hat zwei zusätzliche Spalten, die den Feldern des eingebetteten Objekts entsprechen. Da wir die prefix-Eigenschaft für die @Embedded-Annotation verwenden, lauten die Namen der Spalten "note_location_lat" und "note_location_lon", und es ist möglich, auf diese Spalten in Abfragen zu verweisen.

4. Datenzugriffsobjekt

Um auf die Datenbanken des Room zuzugreifen, ist ein DAO-Objekt erforderlich. Das DAO kann entweder als Schnittstelle oder als abstrakte Klasse definiert werden. Um es zu implementieren, kommentieren Sie die Klasse oder Schnittstelle mit @Dao und Sie können auf Daten zugreifen. Obwohl es möglich ist, von einem DAO aus auf mehr als eine Tabelle zuzugreifen, wird im Namen einer guten Architektur empfohlen, das Prinzip der Trennung von Bedenken beizubehalten und ein DAO zu erstellen, das für den Zugriff auf jede Entität verantwortlich ist.

Einfügen, aktualisieren und löschen

Raum bietet eine Reihe praktischer Anmerkungen für die CRUD-Operationen im DAO: @Insert, @Update, @Delete und @Query. Die @Insert-Operation kann eine einzelne Entität, ein array oder eine List von Entitäten als Parameter empfangen. Bei einzelnen Entitäten wird möglicherweise eine long Zeile zurückgegeben, die die Zeile der Einfügung darstellt. Für mehrere Entitäten als Parameter kann stattdessen ein long[] oder ein List<Long> zurückgegeben werden.

Wie Sie sehen, gibt es noch eine weitere Eigenschaft: onConflict. Dies definiert die Strategie, die bei Konflikten mit OnConflictStrategy-Konstanten verfolgt werden soll. Die Optionen sind ziemlich selbsterklärend, wobei ABORT, FAIL und REPLACE die wichtigeren Möglichkeiten sind.

Verwenden Sie zum Aktualisieren von Entitäten die Annotation @Update. Es folgt dem gleichen Prinzip wie @Insert und empfängt einzelne Entitäten oder mehrere Entitäten als Argumente. Room verwendet die empfangende Entität, um seine Werte zu aktualisieren, wobei der PrimaryKey der Entität als Referenz verwendet wird. @Update kann jedoch nur ein int zurückgeben, das die Gesamtzahl der aktualisierten Tabellenzeilen darstellt.

Auch hier kann die @Delete-Annotation nach demselben Prinzip einzelne oder mehrere Entitäten empfangen und ein int mit der Gesamtzahl der aktualisierten Tabellenzeilen zurückgeben. Es verwendet auch den PrimaryKey der Entität, um das Register in der Tabelle der Datenbank zu finden und zu entfernen.

Anfragen stellen

Schließlich führt die Annotation @Query Abfragen in der Datenbank durch. Die Abfragen sind ähnlich aufgebaut wie SQLite-Abfragen, wobei der größte Unterschied darin besteht, dass Sie Argumente direkt von den Methoden erhalten können. Die wichtigste Eigenschaft ist jedoch, dass die Abfragen zur Kompilierzeit überprüft werden, dh der Compiler findet einen Fehler, sobald Sie das Projekt erstellen.

Um eine Abfrage zu erstellen, kommentieren Sie eine Methode mit @Query und schreiben Sie eine SQLite-Abfrage als Wert. Wir werden dem Schreiben von Abfragen nicht allzu viel Aufmerksamkeit schenken, da sie das Standard-SQLite verwenden. Im Allgemeinen verwenden Sie jedoch Abfragen, um mit dem Befehl SELECT Daten aus der Datenbank abzurufen. Auswahlen können Einzel- oder Sammlungswerte zurückgeben.

Es ist wirklich einfach, Parameter an Abfragen zu übergeben. Room leitet den Namen des Parameters unter Verwendung des Namens des Methodenarguments ab. Um darauf zuzugreifen, verwenden Sie :, gefolgt vom Namen.

LiveData-Abfragen

Room wurde entwickelt, um elegant mit LiveData zu arbeiten. Damit eine @Query LiveData zurückgibt, schließen Sie die Standardrückgabe einfach mit LiveData <?> Ein und los geht's.

Danach ist es möglich, das Abfrageergebnis zu beobachten und ganz einfach asynchrone Ergebnisse zu erhalten. Wenn Sie die Leistungsfähigkeit von LiveData nicht kennen, lesen Sie unser Tutorial über die Komponente.

5. Erstellen der Datenbank

Die Datenbank wird von einer abstrakten Klasse erstellt, die mit @Database annotiert ist und die RoomDatabase-Klasse erweitert. Außerdem müssen die Entitäten, die von der Datenbank verwaltet werden, in einem Array in der entities-Eigenschaft in der @Database-Annotation übergeben werden.

Sobald die Datenbankklasse implementiert ist, ist es an der Zeit, sie zu erstellen. Es ist wichtig zu betonen, dass die Datenbankinstanz idealerweise nur einmal pro Sitzung erstellt werden sollte, und der beste Weg, dies zu erreichen, wäre die Verwendung eines Dependency-Injection-Systems wie Dagger. Wir werden uns jetzt jedoch nicht mit DI befassen, da dies außerhalb des Rahmens dieses Tutorials liegt.

Normalerweise können Operationen an einer Room-Datenbank nicht über den UI-Thread ausgeführt werden, da sie blockieren und wahrscheinlich Probleme für das System verursachen. Wenn Sie jedoch die Ausführung im UI-Thread erzwingen möchten, fügen Sie den Build-Optionen allowMainThreadQueries hinzu. Tatsächlich gibt es viele interessante Optionen zum Erstellen der Datenbank, und ich empfehle Ihnen, die RoomDatabase.Builder-Dokumentation zu lesen, um die Möglichkeiten zu verstehen.

6. Datentyp und Datenkonvertierung

Ein Spalten-Datentyp wird automatisch von Room definiert. Das System leitet aus dem Feldtyp ab, welche Art von SQLite-Datentyp angemessener ist. Denken Sie daran, dass die meisten POJOs von Java sofort konvertiert werden. Es ist jedoch erforderlich, Datenkonverter zu erstellen, um komplexere Objekte zu verarbeiten, die von Room nicht automatisch erkannt werden, z. B. Date und Enum.

Damit Room die Datenkonvertierungen verstehen kann, müssen TypeConverter bereitgestellt und diese Konverter in Room registriert werden. Es ist möglich, diese Registrierung unter Berücksichtigung eines bestimmten Kontexts vorzunehmen – wenn Sie beispielsweise den TypeConverter in der Database registrieren, verwenden alle Entitäten der Datenbank den Konverter. Wenn Sie sich bei einer Entität registrieren, können nur die Eigenschaften dieser Entität sie verwenden usw.

Um ein Date-Objekt während der Speichervorgänge von Room direkt in ein Long zu konvertieren und dann beim Konsultieren der Datenbank ein Long in ein Date zu konvertieren, deklarieren Sie zuerst einen TypeConverter.

Registrieren Sie dann den TypeConverter in der Database oder bei Bedarf in einem spezifischeren Kontext.

7. Verwenden von Room in einer App

Die Anwendung, die wir in dieser Serie entwickelt haben, verwendet SharedPreferences, um Wetterdaten zwischenzuspeichern. Da wir nun wissen, wie man Room verwendet, erstellen wir damit einen ausgefeilteren Cache, der es uns ermöglicht, zwischengespeicherte Daten nach Stadt abzurufen und auch das Wetterdatum während des Datenabrufs zu berücksichtigen.

Lassen Sie uns zunächst unsere Entität erstellen. Wir speichern alle unsere Daten nur mit der WeatherMain-Klasse. Wir müssen der Klasse nur einige Anmerkungen hinzufügen, und wir sind fertig.

Wir brauchen auch einen DAO. Das WeatherDAO wird die CRUD-Operationen in unserem Unternehmen verwalten. Beachten Sie, dass alle Abfragen LiveData zurückgeben.

Schließlich ist es Zeit, die Database zu erstellen.

Ok, wir haben jetzt unsere Room-Datenbank konfiguriert. Alles, was Sie noch tun müssen, ist, es mit Dagger zu verbinden und es zu benutzen. Im DataModule stellen wir die Database und das WeatherDAO bereit.

Wie Sie sich erinnern sollten, verfügen wir über ein Repository, das für die Abwicklung aller Datenvorgänge verantwortlich ist. Lassen Sie uns diese Klasse weiterhin für die Raumdatenanfrage der App verwenden. Aber zuerst müssen wir die Methode providesMainRepository des DataModule bearbeiten, um das WeatherDAO in die Klassenkonstruktion einzubeziehen.

Die meisten Methoden, die wir dem MainRepository hinzufügen, sind ziemlich einfach. Es lohnt sich jedoch, clearOldData() genauer zu betrachten. Dadurch werden alle Daten gelöscht, die älter als einen Tag sind, wobei nur die relevanten Wetterdaten in der Datenbank gespeichert werden.

Das MainViewModel ist verantwortlich für die Konsultationen zu unserem Repository. Fügen wir der Room-Datenbank eine Logik hinzu, um unsere Vorgänge zu adressieren. Zuerst fügen wir eine MutableLiveData hinzu, die weatherDB, die für die Abfrage des MainRepository zuständig ist. Dann entfernen wir Verweise auf SharedPreferences, sodass unser Cache nur auf die Room-Datenbank angewiesen ist.

Um unseren Cache relevant zu machen, löschen wir bei jeder neuen Wetterkonsultation alte Daten.

Schließlich speichern wir die Daten jedes Mal in der Room-Datenbank, wenn neues Wetter eingeht.

Sie können den vollständigen Code im GitHub-Repository für diesen Beitrag sehen.

Abschluss

Endlich sind wir am Ende der Android Architecture Components-Reihe angelangt. Diese Tools werden ausgezeichnete Begleiter auf Ihrer Android-Entwicklungsreise sein. Ich rate Ihnen, die Komponenten weiter zu erkunden. Nehmen Sie sich etwas Zeit, um die Dokumentation zu lesen.

Und sieh dir einige unserer anderen Beiträge zur Android-App-Entwicklung hier auf Envato Tuts+ an!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.