В этом уроке рассмотрим, как получать данные из нескольких таблиц. А также разберемся, как использовать аннотацию Relation.
Полный список уроков курса:
- Урок 1. Lifecycle
- Урок 2. LiveData
- Урок 3. LiveData. Дополнительные возможности
- Урок 4. ViewModel
- Урок 5. Room. Основы
- Урок 6. Room. Entity
- Урок 7. Room. Insert, Update, Delete, Transaction
- Урок 8. Room. Query
- Урок 9. Room. RxJava
- Урок 10. Room. Запрос из нескольких таблиц. Relation
- Урок 11. Room. Type converter
- Урок 12. Room. Миграция версий базы данных
- Урок 13. Room. Тестирование
- Урок 14. Paging Library. Основы
- Урок 15. Paging Library. PagedList и DataSource. Placeholders.
- Урок 16. Paging Library. LivePagedListBuilder. BoundaryCallback.
- Урок 17. Paging Library. Виды DataSource
- Урок 18. Android Data Binding. Основы
- Урок 19. Android Data Binding. Код в layout. Доступ к View
- Урок 20. Android Data Binding. Обработка событий
- Урок 21. Android Data Binding. Observable поля. Двусторонний биндинг.
- Урок 22. Android Data Binding. Adapter. Conversion.
- Урок 23. Android Data Binding. Использование с include, ViewStub и RecyclerView.
- Урок 24. Navigation Architecture Component. Введение
- Урок 25. Navigation. Передача данных. Type-safe аргументы.
- Урок 26. Navigation. Параметры навигации
- Урок 27. Navigation. NavigationUI.
- Урок 28. Navigation. Вложенный граф. Global Action. Deep Link.
- Урок 29. WorkManager. Введение
- Урок 30. WorkManager. Критерии запуска задачи.
- Урок 31. WorkManager. Последовательность выполнения задач.
- Урок 32. WorkManager. Передача и получение данных
- Урок 33. Практика. О чем это будет.
- Урок 34. Практика. TodoApp. Список задач.
- Урок 35. Практика. TodoApp. Просмотр задачи
Для примера будем использовать две таблицы: сотрудники и отделы. Каждый сотрудник прикреплен к какому-либо отделу.
Entity объект для отделов:
@Entity public class Department { @PrimaryKey public int id; public String name; }
Entity объект для сотрудников:
@Entity public class Employee { @PrimaryKey public long id; public String name; public int salary; @ColumnInfo(name = "department_id") public int departmentId; }
В поле departmentId хранится id отдела, к которому прикреплен сотрудник.
Мы хотим получить список работников, в котором будет следующая информация: имя работника, его зарплата, наименование его отдела. Для этого нам надо будет написать запрос, который вытащит данные из двух таблиц.
Описываем метод в Dao объекте
@Dao public interface EmployeeDao { @Query("SELECT employee.name, employee.salary, department.name AS department_name " + "FROM employee, department " + "WHERE department.id == employee.department_id") public List<EmployeeDepartment> getEmployeeWithDepartment(); // ... }
Т.к. поле name есть в обоих таблицах, то для отдела переименовываем его в department_name
Обратите внимание на тип объектов, который мы будем получать от этого метода. Это EmployeeDepartment. Нам нужно создать этот объект, и указать в нем все поля, которые мы ожидаем получить от запроса.
public class EmployeeDepartment { public String name; public int salary; @ColumnInfo(name = "department_name") public String departmentName; }
Это не Entity объект, а обычный класс. Поля этого класса должны совпадать с полями результата, который вернет запрос. Room конвертирует результаты запроса в список этих объектов, и мы получим то, что хотели.
Relation
Аннотация Relation также позволяет делать запросы из нескольких таблиц, но структура результата будет немного другой. И нам самим не придется писать сложные запросы. Room все сделает за нас.
Давайте представим, что нам надо получить список отделов. И к каждому отделу должен прилагаться список сотрудников.
Структура для этих данных будет выглядеть так:
public class DepartmentWithEmployees { public int id; public String name; @Relation(parentColumn = "id", entityColumn = "department_id") public List<Employee> employees; }
Это не Entity, а обычный класс. В полях id и name будут данные отдела.
В employees будет список сотрудников этого отдела. Для этого мы помечаем список аннотацией Relation, и Room сам заполнит его для нас. Давайте разбираться, как именно Room поймет, что он должен поместить в этот список. Откуда он будет брать данные и по какому условию?
Тип данных списка - это Employee. Это Entity объект, для него в базе данных создана таблица. Из этой таблицы Room и будет читать данные по сотрудникам. В параметрах parentColumn и entityColumn указываем названия полей, которые участвуют в условии выборки данных. В результате, Room будет искать сотрудников, у которых entityColumn (т.е. department_id) равен parentColumn (т.е. id) отдела. Все найденные сотрудники окажутся в employees.
По требованиям Room, тип employees должен быть List или Set.
Осталось описать метод в Dao:
@Dao public interface DepartmentDao { @Query("SELECT id, name from department") List<DepartmentWithEmployees> getDepartmentsWithEmployees(); // ... }
Это простой запрос, который вытащит необходимые данные по отделу. А запрос по сотрудникам для каждого отдела сделает за нас Room.
В классе DepartmentWithEmployees мы используем поля id и name для данных по отделу. Но класс Department имеет точно такую же структуру - id и name. Поэтому мы в DepartmentWithEmployees можем заменить эти поля на одно поле с типом Department и аннотацией Embedded:
public class DepartmentWithEmployees { @Embedded public Department department; @Relation(parentColumn = "id", entityColumn = "department_id") public List<Employee> employees; }
Предположим, что нам нужны не все данные по сотрудникам, а только некоторые поля. Например, name и salary. Создаем под них класс:
public class EmployeeNameAndSalary { public String name; public int salary; }
И используем его, как тип в Relation-списке
public class DepartmentWithEmployees { public int id; public String name; @Relation(parentColumn = "id", entityColumn = "department_id", entity = Employee.class) public List<EmployeeNameAndSalary> employees; }
А чтобы Room знал, откуда брать данные по сотрудникам, указываем Entity класс Employee в параметре entity.
Relation может быть вложенным. Т.е. в нашем примере класс EmployeeNameAndSalary также может содержать в себе Relation, который будет для каждого сотрудника собирать, например, список техники, записанной на него.
Relation не может быть использован в Entity классах, только в обычных. Relation поле не может задаваться через конструктор. Оно должно быть public или иметь public set-метод.
Relation + Transaction
При использовании Relation, Room выполняет несколько запросов, чтобы собрать все данные. Имеет смысл выполнять все эти запросы в одной транзакции, чтобы получить корректные данные. Для этого можно использовать аннотацию Transaction
@Transaction @Query("SELECT id, name from department") List<DepartmentWithEmployees> getDepartmentsWithEmployees();
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня