- Урок 6. Room. Entity
- Имя таблицы
- Имя поля
- Тип поля
- Модификаторы доступа
- Первичный ключ
- Внешний ключ
- Индекс
- Вложенные объекты
- Ignore
- Select, Insert, Indexes and Foreign Keys on Room Migrations
- Migration and RoomDatabase
- Migration: SELECT and INSERT
- Foreign Keys
- Indexes
- Add a new column
- Database relations with Room
- One-to-one relations
- One-to-many relations
- Many-to-many relations
- Advanced relation use cases
Урок 6. Room. Entity
В этом уроке более подробно рассмотрим возможности Entity. Как задать имя таблицы. Как задать имя или тип поля. Как создать составной или внешний ключ. Как создать индекс. Как использовать вложенные объекты.
Полный список уроков курса:
Имя таблицы
Entity класс используется для создания таблицы. По умолчанию в качестве имени таблицы используется имя этого класса. Но мы можем указать свое имя, используя параметр tableName.
Для хранения объектов Employee будет создана таблица с именем employees.
Имя поля
По умолчанию в качестве имени полей в таблице используются имена полей Entity класса. Но мы можем указать свое имя, используя параметр name в аннотации ColumnInfo.
Для fullName в таблице будет создано поле с именем full_name.
Тип поля
По умолчанию Room определяет тип данных для поля в таблице по типу данных поля в Entity классе. Но мы можем явно указать свой тип.
В таблице поле salary будет с типом TEXT.
Модификаторы доступа
Чтобы Room мог добраться до полей класса Entity, мы делаем их public.
Но есть возможность использовать private поля. Для этого надо добавить set/get методы.
Все поля — private. Но каждое имеет set/get методы.
В Android Studio эти методы добавляются парой кликов. Жмете в коде ALT+INSERT, выбираете пункт Getter and Setter, затем выбираете поля, для которых надо создать методы.
Вместо set-методов мы также можем использовать конструктор.
Поле id здесь — private и имеет get-метод. А вместо set-метода, Room будет использовать конструктор.
Параметр конструктора должен иметь тот же тип и имя, что и поле Entity класса. Вы можете использовать конструктор для всех полей или только для некоторых, как в примере выше.
Я для упрощения примеров везде буду использовать public поля.
Первичный ключ
Мы уже знаем, как с помощью @PrimaryKey назначить какое-либо поле ключом. Каждый Entity класс должен содержать хотя бы одно такое поле. Даже если в классе всего одно поле.
У PrimaryKey есть параметр autoGenerate. Он позволяет включить для поля режим autoincrement, в котором база данных сама будет генерировать значение, если вы его не укажете.
Теперь при создании Entity объекта вы можете не заполнять поле id. База сама найдет ближайшее свободное значение и использует его.
Чтобы создать составной ключ, используйте параметр primaryKeys.
Внешний ключ
Внешние ключи позволяют связывать таблицы между собой. Если вы еще не знакомы с ними, то можете почитать о них в инете.
В выше рассмотренных примерах у нас есть класс Employee для хранения данных по сотрудникам. Давайте создадим класс Car для хранения данных по машинам. И каждая машина должна быть прикреплена к какому-либо сотруднику.
В поле employee_id будет храниться id сотрудника, к которому прикреплена эта машина.
Используем параметр foreignKeys для создания внешнего ключа. Указываем, что значения поля employee_id (параметр childColumns) должно обязательно быть равно какому-либо значению поля id (параметр parentColumns) в таблице сотрудников Employee (параметр entity).
Т.е. если у нас есть три сотрудника с id 1,2 и 3, мы не сможем добавить в базу данных машину с employee_id = 4. Потому что в базе нет такого родительского ключа, т.е. сотрудника с >
Или, если вы попытаетесь удалить родительский ключ, т.е. сотрудника, к которому прикреплена какая-либо машина, то база выдаст вам ошибку. Потому что после удаления сотрудника, у машины в поле employee_id будет находиться значение, которого нет в поле id таблицы сотрудников.
Для подобных случаев удаления или изменения родительского ключа, вы можете настроить поведение базы данных. По умолчанию она возвращает ошибку, но это можно поменять с помощью параметров onDelete и onUpdate в аннотации ForeignKey.
Добавим параметр onDelete
Его значение = CASCADE. Это означает, что при удалении родительского ключа, будут удалены, связанные с ним дочерние ключи. Т.е. при удалении сотрудника, удалится и его машина.
Список возможных значений для параметра onDelete можно посмотреть здесь. А подробнее почитать о них на русском здесь.
Еще один параметр аннотации ForeignKey — это deferred, имеющий по умолчанию значение false. Если задать этому параметру значение true, то внешний ключ станет отложенным. Это может быть полезно при вставке данных в разные таблицы в рамках одной транзакции. Вы сможете внести все необходимые изменения, и проверка на корректность внешних ключей будет выполнена в самом конце, при выполнении commit.
Подробнее об этом можно почитать здесь.
Индекс
Индексы могут повысить производительность вашей таблицы. Если вы еще не знакомы с ними, то можете почитать о них в инете.
В аннотации Entity есть параметр indicies, который позволяет задавать индексы.
Создаем два индекса: один по полю salary, а другой по двум полям first_name и last_name.
Индекс дает возможность установить для его полей проверку на уникальность. Это делается параметром unique = true.
В этом случае база будет следить, чтобы в этой таблице не было записи с повторящейся парой значений first_name и last_name.
Индекс для одного поля также может быть настроен через параметр index аннотации ColumnInfo
Будет создан индекс для поле salary.
Вложенные объекты
Пусть у нас есть класс Address, с данными о адресе. Это обычный класс, не Entity.
И мы хотим использовать его в Entity классе Employee
Если мы сделаем так, то Room будет ругаться, т.к. он не знает, как сохранить такой объект в базу:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Но есть простое решение — использовать аннотацию Embedded.
Embedded подскажет Room, что надо просто взять поля из Address и считать их полями таблицы Employee.
Т.е. в базе будет создана таблица Employee с полями id, name, salary, city, street, number.
Добавление новой записи будет выглядеть так:
Мы создаем вложенный объект Address, но Room разберется, и запишет все в таблицу, как плоскую структуру.
Embedded объекты могут включать в себя другие Embedded объекты.
Если у вас получается так, что совпадают имена каких-то полей в основном объекте и в Embedded объекте, то используйте префикс для Embedded объекта.
В этом случае к именам полей Embedded объекта в таблице будет добавлен указанный префикс.
Ignore
Аннотация Ignore позволяет подсказать Room, что это поле не должно записываться в базу или читаться из нее.
Нам не нужно хранить Bitmap в базе, поэтому добавляем Ignore к этому полю.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Select, Insert, Indexes and Foreign Keys on Room Migrations
A practical overview of Migration from SQLite to Room
In the new project, we are working on, we are making a huge migration from SQLite to Room, We usually always use Realm, but we decide to give it a try to Coroutines and Room, to handle this huge migration from our side, but I found that sometimes you need a little more information about how to handle some parts of the code. So let’s start with migrations:
Migrations are simpler than in pure SQLite, imagine we have this Data class entity, for our new Room implementation, with a twin but, in SQLite,
Migration and RoomDatabase
All classes that are annotated with Database must extend this class. RoomDatabase provides direct access to the underlying database implementation. Any query apart for the migration should never happen in this layer of the implementation, for that, you can use Dao. You need a migration if there was any kind of database you want to conserve for your users, this is handled with Room, Rooms offers a migration wrapper like the next one:
Migration: SELECT and INSERT
All the entities that your database will need, should be added to the superior part of the abstract class.
We can make a step by step guide for the migration:
1. You need to create a table with your Room entity with a different name
2. Copy all the information, from the SQLite Table to the Room table
3. Drop the table in SQLite
4. Rename the table to have a final entity with the same name that the table on SQLite
Foreign Keys
On the last step, we handle the copy and creation of the table, we use INSERT or REPLACE into the table selected. But this is not good enough yet, for our selected Entity, we need to create the Foreign keys over the UpdatedTableProduct, if we don’t do it, we will face this exception: Foreign key constraint failed, so when we create our table we will add this:
Indexes
The indexes in your table are important for multi-relational tables, for more efficient queries, remember a key to make this really efficient, is not to have many indexes, and this indexes should not change over time, because this will affect the efficiency, to create indexes for our entity:
Add a new column
Imagine your structure of columns change, maybe you add a new column to tell you if the user has viewed the detail of the product, so you need to alter your table first on SQLite:
And this will be our final result, with all these changes, you will have a final migration that is successful, without exceptions, remember if you have more that one table, you probably are going to write the 4 steps to handle the migration a lot.
Источник
Database relations with Room
An important part of designing a relational database is splitting the data into related tables and pulling the data together in meaningful ways. Starting with Room 2.2 (now stable) we have support for all possible relations between tables: one-to-one, one-to-many and many-to-many, with one annotation: @Relation .
One-to-one relations
Let’s say that we live in a (sad) world where a person can own only one dog and a dog can have only one owner. This is a one-to-one relation. To model this in a relational database, we create two tables: Dog and Owner , where the Dog table has a reference to the owner id, or the Owner has a reference to a dog id. In Room, we create two entities:
Let’s say that we want to display the list of all dogs and their owners on the screen. To do this, we would create a DogAndOwner data class:
To query this using SQLite, we would need to 1) run two queries: one that gets all owners, and one that gets all dogs based on owner ids and then 2) handle the object mapping.
To get a List using Room, we don’t need to implement the two queries ourselves and handle the object mapping, but rather, use the @Relation annotation.
In our example, since Dog has the owner’s information, we add the @Relation annotation to the dog variable,: specifying that the ownerId column on the parent (i.e. the Owner entity) corresponds to the dogOwnerId :
Our Dao is now simplified to:
Note: Because Room runs the two queries for us under the hood, add the @Transaction annotation, to ensure that this happens atomically.
One-to-many relations
Let’s say that an owner can have multiple dogs (yay!); we’d have a one-to-many relation between Dog and Owner . The database schema we previously defined doesn’t change — we still have the same tables, since the relating key is already in the “many” table of the relationship.
Now, to display the list of owners with their dogs, we need to create a new data class to model this:
To avoid running two separate queries, we can define a one-to-many relation between Dog and Owner , by annotating the List with @Relation as before:
The Dao becomes:
Many-to-many relations
Now suppose we live in a perfect world where an owner can have multiple dogs, and that a dog can have multiple owners. To model this schema, our Dog and Owner tables are not enough. Since a dog can have multiple owners, we need to have multiple entries of the same dog id, matching to different owner ids. Because dogId is the primary key in Dog , we can’t insert multiple dogs with the same id. To overcome this, we need to create an associative table (also known as cross-reference table) that keeps (dogId,ownerId) pairs:
If we now want to get the list of all owners with dogs: List , using just SQLite queries, we need to write two queries: one that gets all owners and one that joins the Dog and the DogOwnerCrossRef tables:
To implement this in Room, we need to update our OwnerWithDogs data class and tell Room that in order to get the Dogs , it needs to use the DogOwnerCrossRef associate table. We reference the table by using a Junction :
In our Dao, we need to select from Owners and return the right data class:
Advanced relation use cases
When using the @Relation annotation, Room infers the entity to use from the type of the annotated property by default. For example, until now we annotated a Dog (or a List ) with @Relation , telling Room how to model the class and which columns to query
If we want to return a different object, for example a Pup , that is not an entity but contains some of the fields, we can specify the entity to use in the @Relation annotation:
If we want to return only specific columns from an entity you need to tell Room which these are by defining them in the projection property of the @Relation . For example, let’s say that we just want to get the names of all the dogs in our OwnerWithDogs data class. Since we would need a List , Room can’t deduce whether those strings correspond to the name or to the breed, so we need to specify the column in the projection:
If you want to define a stricter relationship between the dogOwnerId and ownerId , independent of what kind of relation you’re creating, use a ForeignKey constraint between the fields. Keep in mind that SQLite foreign keys define indices and can have cascading triggers that update or delete entries in your tables. So decide whether you want to use foreign keys based on whether you do want this kind of functionality in your database.
Whether you need one-to-one, one-to-many or many-to-many support, Room has you (and your doggos) covered with one annotation: @Relation . Find out more about Room 2.2 features from our Android Dev Summit ’19 talk:
Источник