
Room ist eine Abstraktionsschicht über SQLite, die die Speicherung vereinfacht. Wenn Sie Room noch nicht kennen, lesen Sie diesen Einführungsartikel:
7 Schritte zur Verwendung von Room. Exemplarische Vorgehensweise zum Migrieren Ihrer Anwendung nach Room
In diesem Artikel möchte ich einige Tipps geben, wie Sie Room so effizient wie möglich nutzen können.
1. Füllen Sie die Datenbank vorab aus
Müssen Sie Ihrer Datenbank unmittelbar nach ihrer Erstellung oder zum Zeitpunkt des ersten Zugriffs darauf Standarddaten hinzufügen? Verwenden Sie RoomDatabase # Callback . Rufen Sie beim Erstellen Ihrer Datenbank die Methode addCallback auf und überschreiben Sie entweder onCreate oder onOpen .
onCreate
wird beim ersten onCreate
der Datenbank unmittelbar nach dem onCreate
der Tabellen aufgerufen. onOpen
wird aufgerufen, wenn die Datenbank geöffnet wird. Da der Zugriff auf das DAO erst nach Abschluss dieser Methoden möglich ist, erstellen wir einen neuen Stream, in dem wir einen Link zur Datenbank erhalten. Anschließend erhalten wir das DAO und fügen die erforderlichen Daten ein.
Room.databaseBuilder(context.applicationContext, DataDatabase::class.java, "Sample.db") // prepopulate the database after onCreate was called .addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db)
Das vollständige Beispiel finden Sie hier .
Hinweis: Wenn Sie den ioThread
Ansatz verwenden und die Anwendung beim ersten Start zwischen dem Erstellen der Datenbank und dem Einfügen abstürzt, werden die Daten niemals eingefügt.
2. Verwenden von DAO-Vererbungsfunktionen
Haben Sie mehrere Tabellen in Ihrer Datenbank und kopieren Sie dieselben Methoden zum Einfügen , Aktualisieren und Löschen ? DAOs unterstützen die Vererbung. Erstellen Sie daher eine BaseDao<T>
-Klasse und definieren Sie dort Ihre allgemeinen Methoden @Insert
, @Update
und @Delete
. Lassen Sie jedes DAO BaseDao
und Methoden hinzufügen, die für jedes von ihnen spezifisch sind.
interface BaseDao<T> { @Insert fun insert(vararg obj: T) } @Dao abstract class DataDao : BaseDao<Data>() { @Query("SELECT * FROM Data") abstract fun getData(): List<Data> }
Details finden Sie hier .
DAOs müssen Schnittstellen oder abstrakte Klassen sein, da Room ihre Implementierung zur Kompilierungszeit generiert, einschließlich Methoden von BaseDao
.
3. Ausführen von Abfragen in Transaktionen ohne Boilerplate-Code
Durch das Annotieren einer Methode mit @Transaction wird sichergestellt, dass alle Datenbankoperationen, die Sie in dieser Methode ausführen, innerhalb derselben Transaktion ausgeführt werden. Eine Transaktion wird nicht ausgeführt, wenn im Methodenkörper eine Ausnahme auftritt.
@Dao abstract class UserDao { @Transaction open fun updateData(users: List<User>) { deleteAllUsers() insertAll(users) } @Insert abstract fun insertAll(users: List<User>) @Query("DELETE FROM Users") abstract fun deleteAllUsers() }
Möglicherweise möchten Sie die Annotation @Transaction
für @Transaction
Methoden verwenden, die die select
Anweisung in folgenden Fällen verwenden:
- Wenn das Abfrageergebnis ziemlich groß ist. Indem Sie eine Anfrage in einer Transaktion stellen, stellen Sie sicher, dass das Ergebnis der Anfrage, wenn es nicht in einen „Teil“ des Cursors passt, nicht durch Änderungen in der Datenbank zwischen den Cursorpermutationen beschädigt wird.
- Wenn das Abfrageergebnis ein POJO mit
@Relation
Feldern ist. Jedes Feld ist eine Anforderung für sich. Wenn Sie sie also in einer Transaktion ausführen, werden konsistente Ergebnisse zwischen den Anforderungen sichergestellt.
Die @Insert
@Delete
, @Update
und @Insert
, die mehrere Parameter haben, werden automatisch innerhalb der Transaktion gestartet.
4. Lesen Sie nur das, was Sie brauchen
Verwenden Sie beim Anfordern der Datenbank alle Felder, die Sie in der Antwort erhalten? Achten Sie auf die von Ihrer Anwendung verwendete Speichermenge und laden Sie nur die Felder herunter, die Sie letztendlich verwenden werden. Dies erhöht auch die Geschwindigkeit Ihrer Anfragen, indem die Kosten für E / A gesenkt werden. Room übernimmt für Sie die Zuordnung zwischen den Spalten und dem Objekt.
Betrachten Sie dieses komplexe User
:
@Entity(tableName = "users") data class User(@PrimaryKey val id: String, val userName: String, val firstName: String, val lastName: String, val email: String, val dateOfBirth: Date, val registrationDate: Date)
Auf einigen Bildschirmen müssen nicht alle diese Informationen angezeigt werden. Daher können wir stattdessen ein UserMinimal
Objekt erstellen, das nur die erforderlichen Daten enthält.
data class UserMinimal(val userId: String, val firstName: String, val lastName: String)
In der DAO-Klasse definieren wir die Abfrage und wählen die richtigen Spalten aus der users
.
@Dao interface UserDao { @Query(“SELECT userId, firstName, lastName FROM Users) fun getUsersMinimal(): List<UserMinimal> }
5. Kontrolle der Abhängigkeiten zwischen Entitäten mit Fremdschlüsseln
Obwohl Room Beziehungen zwischen Entitäten nicht direkt unterstützt , können Sie Abhängigkeiten zwischen Objekten mithilfe von Fremdschlüsseln definieren.
Der Raum verfügt über die Annotation @ForeignKey , die Teil der Annotation @Entity ist . In der Funktionalität ähnelt es Fremdschlüsseln in SQLite . Es garantiert die Beibehaltung der Beziehungen zwischen Entitäten, wenn Änderungen an der Datenbank vorgenommen werden. Definieren Sie zum Hinzufügen das Objekt, auf das verwiesen werden soll, sowie die Spalten im aktuellen Objekt und das Volume, auf das Sie sich beziehen.
Betrachten Sie die Klasse User
und Pet
. Pet
hat einen Besitzer - die Benutzer-ID, auf die der Fremdschlüssel verweist.
@Entity(tableName = "pets", foreignKeys = arrayOf( ForeignKey(entity = User::class, parentColumns = arrayOf("userId"), childColumns = arrayOf("owner")))) data class Pet(@PrimaryKey val petId: String, val name: String, val owner: String)
Wenn Sie möchten, können Sie festlegen, welche Aktion ausgeführt werden soll, wenn das übergeordnete Element in der Datenbank gelöscht oder aktualisiert wird. Sie können eine der folgenden Optionen NO_ACTION
: NO_ACTION
, RESTRICT
, SET_NULL
, SET_DEFAULT
oder CASCADE
, die sich wie in SQLite verhalten.
Hinweis: In Room funktioniert SET_NULL
wie SET_NULL
, weil In Room können Sie noch keine Standardwerte für Spalten festlegen.
6. Vereinfachen Sie Eins-zu-Viele-Abfragen mit @Relation
Im vorherigen Beispiel User
- Pet
können wir sagen, dass es eine Eins-zu-Viele- Beziehung gibt: Der Benutzer kann mehrere Haustiere haben. Angenommen, wir möchten eine Liste der Benutzer mit ihren Haustieren erhalten: List<UserAndAllPets>
.
data class UserAndAllPets (val user: User, val pets: List<Pet> = ArrayList())
Um dies manuell zu tun, benötigen wir zwei Abfragen: eine, um eine Liste aller Benutzer zu erhalten, und die andere, um eine Liste der Haustiere basierend auf der Benutzer-ID zu erhalten.
@Query(“SELECT * FROM Users”) public List<User> getUsers(); @Query(“SELECT * FROM Pets where owner = :userId”) public List<Pet> getPetsForUser(String userId);
Dann werden wir die Liste der Benutzer durchlaufen und jedes Mal auf die Tabelle " Pets
zugreifen.
@Relation Annotation erleichtert unser Leben: Es werden automatisch verwandte Objekte angefordert . @Relation
kann nur auf List
oder Set
angewendet werden. Aktualisieren Sie die UserAndAllPets-Klasse:
class UserAndAllPets { @Embedded var user: User? = null @Relation(parentColumn = “userId”, entityColumn = “owner”) var pets: List<Pet> = ArrayList() }
Im DAO definieren wir eine Abfrage, und Room fragt die Users
und Pets
und ordnet die Objekte selbst zu.
@Transaction @Query(“SELECT * FROM Users”) List<UserAndAllPets> getUsers();
7. Vermeiden Sie falsche Benachrichtigungen für beobachtbare Anfragen
Angenommen, Sie möchten einen Benutzer anhand einer beobachtbaren Anforderung anhand seiner Kennung ermitteln:
@Query(“SELECT * FROM Users WHERE userId = :id) fun getUserById(id: String): LiveData<User>
Sie erhalten jedes Mal ein neues User
wenn es aktualisiert wird. Sie erhalten dieses Objekt jedoch auch, wenn andere Änderungen in der Users
(Löschungen, Aktualisierungen oder Einfügungen) auftreten, die nichts mit dem Benutzer zu tun haben, an dem Sie interessiert sind, was zu falschen Benachrichtigungen führt. Wenn Ihre Abfrage mehrere Tabellen enthält, erhalten Sie außerdem neue Nachrichten, wenn sich an einer dieser Tabellen etwas ändert.
Folgendes passiert hinter den Kulissen:
- SQLite hat Trigger , die
DELETE
, wenn DELETE
, UPDATE
oder INSERT
in einer Tabelle auftreten. - Room erstellt einen InvalidationTracker , der
Observers
, die alle Änderungen in den beobachteten Tabellen verfolgen. - Sowohl
LiveData
als auch Flowable
Anforderungen Flowable
auf der Benachrichtigung InvalidationTracker.Observer # onInvalidated . Wenn es empfangen wird, tritt eine wiederholte Anforderung auf.
Der Raum weiß nur, dass der Tisch geändert wurde, weiß aber nicht, warum und was sich geändert hat. Daher wird nach einer zweiten Anforderung das Ergebnis der Anforderung mit LiveData
oder Flowable
. Weil Der Raum speichert keine Daten im Speicher und kann nicht feststellen, ob es sich um dieselben Daten handelt oder nicht.
Sie müssen sicherstellen, dass Ihr DAO Anforderungen filtert und nur auf die erforderlichen Objekte reagiert.
Wenn die beobachtbare Abfrage mit Flowables
implementiert Flowables
, verwenden Sie Flowable # diverUntilChanged .
@Dao abstract class UserDao : BaseDao<User>() { @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): Flowable<User> fun getDistinctUserById(id: String): Flowable<User> = getUserById(id) .distinctUntilChanged() }
Wenn Ihre Abfrage LiveData
, können Sie MediatorLiveData
, das nur die gewünschten Objekte von der Quelle empfängt.
fun <T> LiveData<T>.getDistinct(): LiveData<T> { val distinctLiveData = MediatorLiveData<T>() distinctLiveData.addSource(this, object : Observer<T> { private var initialized = false private var lastObj: T? = null override fun onChanged(obj: T?) { if (!initialized) { initialized = true lastObj = obj distinctLiveData.postValue(lastObj) } else if ((obj == null && lastObj != null) || obj != lastObj) { lastObj = obj distinctLiveData.postValue(lastObj) } } }) return distinctLiveData }
In Ihren DAOs ist die Methode, die LiveData
zurückgibt, public
und die Methode, die die Datenbank abfragt, ist protected
.
@Dao abstract class UserDao : BaseDao<User>() { @Query(“SELECT * FROM Users WHERE userid = :id”) protected abstract fun getUserById(id: String): LiveData<User> fun getDistinctUserById(id: String): LiveData<User> = getUserById(id).getDistinct() }
Das vollständige Codebeispiel finden Sie hier .
Hinweis: Wenn Sie eine Liste zur Anzeige anfordern, beachten Sie die Paging-Bibliothek , die einen LivePagedListBuilder zurückgibt . Die Bibliothek hilft Ihnen dabei, die Differenz zwischen Listenelementen automatisch zu berechnen und Ihre Benutzeroberfläche zu aktualisieren.
Siehe auch: 7 Schritte zur Verwendung von Room. Exemplarische Vorgehensweise zum Migrieren Ihrer Anwendung nach Room