Выберите типы отношений между объектами

Поскольку SQLite — это реляционная база данных, вы можете определять связи между сущностями. Но в то время как большинство библиотек объектно-реляционного отображения позволяют объектам сущностей ссылаться друг на друга, Room явно это запрещает. Чтобы узнать о технических причинах этого решения, см. раздел «Понимание того, почему Room не разрешает ссылки на объекты» .

Типы отношений

Room поддерживает следующие типы отношений:

  • «Один к одному» : обозначает отношение, при котором один объект связан с другим единственным объектом.
  • «Один ко многим» : обозначает отношение, при котором один объект может быть связан с несколькими объектами другого типа.
  • Связь «многие ко многим» : представляет собой отношение, при котором несколько сущностей одного типа могут быть связаны с несколькими сущностями другого типа. Обычно для этого требуется таблица связей.
  • Вложенные связи с использованием встроенных объектов : представляет собой связь, в которой одна сущность содержит другую сущность в качестве свойства, и эта вложенная сущность может, в свою очередь, содержать другие сущности. Для этого используется аннотация @Embedded .

Выберите один из двух подходов.

В Room существует два способа определения и запроса связей между сущностями. Вы можете использовать любой из них:

  • Промежуточный класс данных со встроенными объектами, или
  • Функция реляционного запроса с возвращаемым типом multimap.

Если у вас нет конкретной причины использовать промежуточные классы данных, мы рекомендуем использовать подход с возвращаемым типом multimap. Подробнее об этом подходе см. в разделе «Возвращение multimap» .

Использование промежуточных классов данных позволяет избежать написания сложных SQL-запросов, но также может привести к увеличению сложности кода, поскольку требует дополнительных классов данных. Короче говоря, подход с возвращаемым типом multimap требует от ваших SQL-запросов большей работы, а подход с промежуточными классами данных требует от вашего кода большей работы.

Используйте подход с промежуточными классами данных.

При использовании промежуточного класса данных вы определяете класс данных, который моделирует взаимосвязь между вашими сущностями Room. Этот класс данных содержит пары между экземплярами одной сущности и экземплярами другой сущности в виде встроенных объектов . Ваши функции запросов затем могут возвращать экземпляры этого класса данных для использования в вашем приложении.

Например, вы можете определить класс данных UserBook для представления пользователей библиотеки, у которых взяты напрокат определенные книги, и определить функцию запроса для получения списка экземпляров UserBook из базы данных:

@Dao
interface UserBookDao {
    @Query(
        """
        SELECT user.name AS userName, book.name AS bookName
        FROM user JOIN book ON user.id = book.user_id
        """
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>
}

data class UserBook(val userName: String, val bookName: String)

Используйте подход с возвращаемым типом multimap.

При использовании многоуровневого типа возвращаемого значения вам не нужно определять какие-либо дополнительные классы данных. Вместо этого вы определяете многоуровневый тип возвращаемого значения для вашей функции на основе желаемой структуры карты и определяете взаимосвязь между вашими сущностями непосредственно в вашем SQL-запросе.

Например, следующая функция запроса возвращает сопоставление экземпляров User и Book , представляющих пользователей библиотеки, у которых есть определенные книги, взятые напрокат:

@Query(
    """
    SELECT *
    FROM user JOIN book ON user.id = book.user_id
    """
)
suspend fun loadUserAndBookNames(): Map<User, List<Book>>

С помощью типов возвращаемых значений multimap вы также можете запрашивать отношения «один к одному», которые не включают другую сущность. Следующая функция запроса возвращает сопоставление User и количества взятых им напрокат книг с использованием аннотации @MapColumn :

@Query(
    """
    SELECT user.*, COUNT(book.id) AS book_count
    FROM user LEFT JOIN book ON user.id = book.user_id
    GROUP BY user.id
    """
)
suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>

Создание встроенных объектов

Иногда возникает необходимость представить сущность или объект данных как единое целое в логике базы данных, даже если объект содержит несколько свойств. В таких ситуациях используйте аннотацию @Embedded для разложения объекта на его под-свойства внутри таблицы. Затем вы можете запрашивать встроенные свойства так же, как и другие столбцы.

Например, ваш класс User может включать свойство Address , представляющее собой комбинацию свойств street , city , state и postCode . Чтобы хранить составные столбцы отдельно в таблице, аннотируйте свойство Address в классе User с помощью @Embedded . Следующий фрагмент кода демонстрирует эту настройку:

data class Address(
    val street: String?,
    val state: String?,
    val city: String?,
    @ColumnInfo(name = "post_code") val postCode: Int
)

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String,
    @Embedded val address: Address?
)

Таблица, представляющая объект User , содержит столбцы со следующими именами: id , firstName , street , state , city и post_code .

Если у сущности есть несколько встроенных свойств одного типа, вы можете обеспечить уникальность каждого столбца, задав свойство prefix . Затем Room добавляет предоставленное значение в начало имени каждого столбца во встроенном объекте.