Как выбрать типы связей между объектами

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

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

Для переговорных комнат поддерживаются следующие типы связей:

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

Как выбрать подход

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

  • промежуточный класс данных со встроенными объектами;
  • Функция реляционного запроса с типом возвращаемого значения 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

При использовании типа возвращаемого значения multimap не нужно определять дополнительные классы данных. Вместо этого вы определяете тип возвращаемого значения 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. Чтобы сохранить составные столбцы в таблице отдельно, добавьте аннотацию @Embedded к свойству Address в классе User. Ниже приведен фрагмент кода, в котором показана эта настройка:

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 добавит указанное значение в начало каждого названия столбца во встроенном объекте.