Room может преобразовывать примитивные и упакованные типы, но не позволяет использовать ссылки на объекты между объектами. Узнайте, как использовать конвертеры типов и почему Room не поддерживает ссылки на объекты.
Как использовать конвертеры типов
Иногда приложению требуется хранить пользовательский тип данных в одном столбце базы данных. Чтобы поддерживать пользовательские типы, нужно предоставить конвертеры типов. Это функции, которые сообщают Room, как преобразовывать пользовательские типы в известные типы, которые Room может сохранять, и наоборот. Конвертеры типов определяются с помощью аннотации @ColumnTypeConverter.
Предположим, вам нужно сохранить экземпляры Date в базе данных Room. Room не может хранить объекты типа Date, поэтому вам нужно определить конвертеры типов:
object Converters { @ColumnTypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @ColumnTypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time } }
В этом примере определены две функции преобразования типов: одна преобразует объект Date в объект Long, а другая – объект Long в объект Date. Поскольку Room может сохранять объекты Long, он может использовать эти конвертеры для сохранения объектов Date.
Затем добавьте аннотацию @ColumnTypeConverters к классу AppDatabase, чтобы Room мог использовать определенный вами класс конвертера:
@Database(entities = [User::class], version = 1) @ColumnTypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
После того как вы определили эти преобразователи типов, вы можете использовать свой специальный тип в объектах и DAO так же, как и примитивные типы:
@Entity data class User( @PrimaryKey val id: Long, val name: String, val birthday: Date? ) @Dao interface UserDao { @Query("SELECT * FROM user WHERE birthday = :targetDate") suspend fun findUsersBornOnDate(targetDate: Date): List<User> }
Поскольку в этом примере вы аннотировали AppDatabase с помощью @ColumnTypeConverters, Room может использовать определенный конвертер типов везде. Чтобы ограничить область действия конвертеров типа определенными объектами или DAO, добавьте аннотацию @ColumnTypeConverters к классам @Entity или @Dao.
Инициализация конвертера типа параметра
Обычно Room создает конвертеры типов автоматически. Однако если вам нужно передать дополнительные зависимости классам преобразователей типов, инициализацию этих классов должно контролировать ваше приложение. В этом случае добавьте к классу конвертера аннотацию @ProvidedColumnTypeConverter:
@ProvidedColumnTypeConverter class ExampleConverter { @ColumnTypeConverter fun stringToExample(string: String?): ExampleType? { return string?.let { ExampleType() } } @ColumnTypeConverter fun exampleToString(example: ExampleType?): String? { return example?.toString() } }
Помимо объявления класса конвертера в @ColumnTypeConverters, используйте функцию RoomDatabase.Builder.addColumnTypeConverter, чтобы передать экземпляр класса конвертера в конструктор RoomDatabase:
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addColumnTypeConverter(exampleConverterInstance) .build()
Почему Room не поддерживает ссылки на объекты
Ключевой вывод. Room не допускает ссылок на объекты между классами объектов. Вместо этого вы должны явно запросить данные, необходимые вашему приложению.
Сопоставление отношений из базы данных с соответствующей объектной моделью – распространенная практика, которая хорошо работает на стороне сервера. Даже если программа загружает свойства по мере доступа к ним, сервер работает хорошо.
Однако на стороне клиента такой тип отложенной загрузки невозможен, поскольку обычно она выполняется в потоке UI, а запрос информации на диске в потоке UI приводит к значительным проблемам с производительностью. У потока UI обычно есть около 16 мс на то, чтобы рассчитать и отрисовать обновленный макет activity. Поэтому, даже если запрос занимает всего 5 мс, скорее всего, у приложения не хватит времени на отрисовку кадра, что приведет к заметным визуальным ошибкам. Если параллельно выполняется другая транзакция или устройство выполняет другие задачи, требующие интенсивного использования диска, запрос может выполняться ещё дольше. Если вы не используете ленивую загрузку, приложение получает больше данных, чем нужно, что приводит к проблемам с потреблением памяти.
Объектно-реляционные преобразования обычно оставляют это решение за разработчиками, чтобы они могли выбрать оптимальный вариант для своего приложения. Обычно разработчики решают, будет ли модель использоваться в приложении и интерфейсе. Однако это решение не масштабируется, поскольку по мере изменения интерфейса общая модель создает проблемы, которые разработчикам сложно предвидеть и отлаживать.
Например, в интерфейсе загружается список объектов Book, и у каждой книги есть объект Author. Вы можете изначально создать запросы с отложенной загрузкой, чтобы экземпляры Book получали информацию об авторе. При первом получении свойства author выполняется запрос к базе данных. Через некоторое время вы понимаете, что имя автора нужно показывать и в интерфейсе приложения. Вы можете получить доступ к этому названию, как показано в следующем фрагменте кода:
Text(text = book.author.name)
Однако это, казалось бы, безобидное изменение приводит к тому, что таблица Author запрашивается в основном потоке.
Если вы заранее запрашиваете информацию об авторе, но она вам не нужна, будет сложно изменить способ загрузки данных. Например, если интерфейсу приложения больше не нужно показывать информацию Author, оно будет загружать данные, которые не отображаются, и занимать ценное пространство в памяти. Эффективность приложения снижается ещё сильнее, если класс Author ссылается на другую таблицу, например Books.
Чтобы одновременно ссылаться на несколько объектов с помощью Room, создайте объект данных, содержащий каждый объект, а затем напишите запрос, который объединяет соответствующие таблицы. Благодаря этой хорошо структурированной модели и надежным возможностям проверки запросов Room ваше приложение будет потреблять меньше ресурсов при загрузке данных, что повысит его производительность и удобство использования.