Room bietet Funktionen zum Konvertieren zwischen einfachen und Box-Typen, erlaubt jedoch keine Objektverweise zwischen Entitäten. In diesem Dokument wird erläutert, wie Typkonverter verwendet werden und warum Room keine Objektverweise unterstützt.
Typkonverter verwenden
Manchmal muss Ihre Anwendung einen benutzerdefinierten Datentyp in einer einzelnen Datenbankspalte speichern. Sie unterstützen benutzerdefinierte Typen, indem Sie Typkonverter angeben. Das sind Methoden, mit denen Room angewiesen wird, wie benutzerdefinierte Typen in bekannte und von bekannten Typen konvertiert werden sollen, die „Room“ beibehalten werden kann. Typkonverter werden mithilfe der Annotation @TypeConverter
identifiziert.
Angenommen, Sie müssen Instanzen von Date
in der Raumdatenbank dauerhaft speichern. Room weiß nicht, wie Date
-Objekte beibehalten werden sollen. Daher müssen Sie Typkonverter definieren:
Kotlin
class Converters { @TypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time?.toLong() } }
Java
public class Converters { @TypeConverter public static Date fromTimestamp(Long value) { return value == null ? null : new Date(value); } @TypeConverter public static Long dateToTimestamp(Date date) { return date == null ? null : date.getTime(); } }
In diesem Beispiel werden zwei Methoden zur Typkonverter definiert: eine, die ein Date
-Objekt in ein Long
-Objekt konvertiert, und eine, die die umgekehrte Konvertierung von Long
in Date
durchführt. Room weiß, wie Long
-Objekte beibehalten werden sollen, und kann diese Konvertierungsprogramme verwenden, um Date
-Objekte dauerhaft zu speichern.
Als Nächstes fügen Sie der AppDatabase
-Klasse die Annotation @TypeConverters
hinzu, damit Room die von Ihnen definierte Converter-Klasse kennt:
Kotlin
@Database(entities = [User::class], version = 1) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
Java
@Database(entities = {User.class}, version = 1) @TypeConverters({Converters.class}) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); }
Wenn diese Typkonverter definiert sind, können Sie den benutzerdefinierten Typ in Ihren Entitäten und DAOs genau wie primitive Typen verwenden:
Kotlin
@Entity data class User(private val birthday: Date?) @Dao interface UserDao { @Query("SELECT * FROM user WHERE birthday = :targetDate") fun findUsersBornOnDate(targetDate: Date): List<User> }
Java
@Entity public class User { private Date birthday; } @Dao public interface UserDao { @Query("SELECT * FROM user WHERE birthday = :targetDate") List<User> findUsersBornOnDate(Date targetDate); }
In diesem Beispiel kann Room den definierten Typkonverter überall verwenden, da Sie AppDatabase
mit @TypeConverters
annotiert haben. Sie können jedoch auch Bereichstypkonverter in bestimmte Entitäten oder DAOs umwandeln. Dazu annotieren Sie die Klassen @Entity
oder @Dao
mit @TypeConverters
.
Initialisierung des Steuerelementtypkonverters
Normalerweise übernimmt Room die Instanziierung der Typkonverter für Sie. Manchmal müssen Sie jedoch zusätzliche Abhängigkeiten an Ihre Typkonverterklassen übergeben. In diesem Fall muss Ihre Anwendung die Initialisierung der Typkonverter direkt steuern. Annotieren Sie in diesem Fall die Konvertierungsklasse mit @ProvidedTypeConverter
:
Kotlin
@ProvidedTypeConverter class ExampleConverter { @TypeConverter fun StringToExample(string: String?): ExampleType? { ... } @TypeConverter fun ExampleToString(example: ExampleType?): String? { ... } }
Java
@ProvidedTypeConverter public class ExampleConverter { @TypeConverter public Example StringToExample(String string) { ... } @TypeConverter public String ExampleToString(Example example) { ... } }
Zusätzlich zur Deklaration der Konvertierungsklasse in @TypeConverters
verwenden Sie die Methode RoomDatabase.Builder.addTypeConverter()
, um eine Instanz Ihrer Konvertierungsklasse an den RoomDatabase
-Builder zu übergeben:
Kotlin
val db = Room.databaseBuilder(...) .addTypeConverter(exampleConverterInstance) .build()
Java
AppDatabase db = Room.databaseBuilder(...) .addTypeConverter(exampleConverterInstance) .build();
Warum in Room keine Objektverweise zulässig sind
Wichtigste Erkenntnis: Der Raum erlaubt keine Objektverweise zwischen Entitätsklassen. Stattdessen müssen Sie die Daten, die Ihre Anwendung benötigt, explizit anfordern.
Das Zuordnen von Beziehungen aus einer Datenbank zum jeweiligen Objektmodell ist eine gängige Praxis und funktioniert serverseitig sehr gut. Auch wenn Felder beim Zugriff durch das Programm geladen werden, funktioniert der Server weiterhin gut.
Auf Clientseite ist diese Art von Lazy Loading jedoch nicht möglich, da sie normalerweise im UI-Thread erfolgt und das Abfragen von Informationen auf dem Laufwerk im UI-Thread erhebliche Leistungsprobleme mit sich bringt. Der UI-Thread hat in der Regel etwa 16 ms, um das aktualisierte Layout einer Aktivität zu berechnen und zu zeichnen. Selbst wenn eine Abfrage nur 5 ms dauert, ist es also immer noch wahrscheinlich, dass der App die Zeit zum Zeichnen des Frames abläuft und es zu deutlichen visuellen Störungen kommt. Die Abfrage kann noch länger dauern, wenn eine separate Transaktion parallel ausgeführt wird oder das Gerät andere laufwerksintensive Aufgaben ausführt. Wenn Sie Lazy Loading nicht verwenden, ruft Ihre Anwendung jedoch mehr Daten ab, als erforderlich sind, was zu Problemen mit der Speicherauslastung führt.
Bei objektrelationalen Zuordnungen müssen die Entwickler diese Entscheidung in der Regel selbst treffen, damit sie das tun können, was für die Anwendungsfälle ihrer Anwendung am besten ist. Entwickler entscheiden in der Regel, das Modell für ihre App und die UI freizugeben. Diese Lösung lässt sich jedoch nicht gut skalieren, da sich die Benutzeroberfläche im Laufe der Zeit ändert und das gemeinsam genutzte Modell Probleme verursacht, die für Entwickler schwer vorherzusehen und zu beheben sind.
Stellen Sie sich beispielsweise eine UI vor, über die eine Liste von Book
-Objekten geladen wird, wobei jedes Buch ein Author
-Objekt enthält. Am Anfang könnten Sie Ihre Abfragen so gestalten, dass Lazy Loading verwendet wird, damit Book
-Instanzen den Autor abrufen. Beim ersten Abrufen des Felds author
wird die Datenbank abgefragt. Etwas später stellen Sie fest, dass Sie auch den Namen des Autors in der Benutzeroberfläche Ihrer App anzeigen müssen. Sie können problemlos auf diesen Namen zugreifen, wie im folgenden Code-Snippet gezeigt:
Kotlin
authorNameTextView.text = book.author.name
Java
authorNameTextView.setText(book.getAuthor().getName());
Diese scheinbar harmlose Änderung führt jedoch dazu, dass die Tabelle Author
im Hauptthread abgefragt wird.
Wenn Sie Autoreninformationen im Voraus abfragen, wird es schwierig, die Art des Ladens der Daten zu ändern, wenn Sie diese Daten nicht mehr benötigen. Wenn auf der Benutzeroberfläche Ihrer App beispielsweise keine Author
-Informationen mehr angezeigt werden müssen, lädt die App effektiv Daten, die nicht mehr angezeigt werden, wodurch wertvoller Speicherplatz verschwendet wird. Die Effizienz der Anwendung verschlechtert sich noch, wenn die Klasse Author
auf eine andere Tabelle verweist, z. B. Books
.
Wenn Sie mit „Room“ auf mehrere Entitäten gleichzeitig verweisen möchten, erstellen Sie stattdessen ein POJO, das alle Entitäten enthält. Schreiben Sie dann eine Abfrage, die die entsprechenden Tabellen verbindet. Dieses gut strukturierte Modell führt in Kombination mit den robusten Funktionen zur Abfragevalidierung von Room dazu, dass Ihre Anwendung beim Laden von Daten weniger Ressourcen verbraucht. Dadurch wird die Leistung und die Nutzerfreundlichkeit Ihrer Anwendung verbessert.