- Урок 6. Room. Entity
- Имя таблицы
- Имя поля
- Тип поля
- Модификаторы доступа
- Первичный ключ
- Внешний ключ
- Индекс
- Вложенные объекты
- Ignore
- @Entity, @Embedded and Composite Primary Keys with Room DB
- Data classes hold data.
- And @Entity makes a table out of the data class.
- But the address variables relate directly to Address and not to Person.
- @Embedded makes this happen.
- Composite Primary Key
- An @Embedded field cannot contain Primary Key.
- There is one more option.
- Room Guide
Урок 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
@Entity, @Embedded and Composite Primary Keys with Room DB
Kinnera Priya Putti
Nov 16, 2018 · 2 min read
Android implementation in Kotlin
@Entity creates a table for each class it’s annotated with, and so the class itself is essentially a data class that doesn’t contain logic.
Data classes hold data.
And @Entity makes a table out of the data class.
The @Entity annotation indicates that the data class is to be mapped to a database.
But the address variables relate directly to Address and not to Person.
Multiple variables relate to Address which in turn relates to Person. Therefore, it does sound more competent to use an object for storing the address.
Then, we would need to access the variables, for example, city, as:
So what if all these variables needed to be directly accessed from Person as person.city?
@Embedded makes this happen.
In a data class, thi s annotation indicates that the instance of the class being embedded is stored as an intrinsic part of the class where the annotation is being used.
@Embedded can be used to ‘snapshot’ the state of a related class inside the parent class.
Meaning, the structuring of the data isn’t actually changed. The fields from the embedded class are merged with the parent class. These fields can be used as if they were declared in the parent class as required.
When multiple fields are closely related, but your data is not structured to create an object enclosing those multiple fields, @Embedded may be used to access the data as required, making the code more readable and maintainable.
Composite Primary Key
In JPA (Java Persistence API), we can add multiple Primary Keys by creating an @Embeddable class. Using it in the @Entity class as @EmbeddedId would be sufficient for a composite Primary Key.
In Kotlin, while @Embedded is certainly a point in favour of Room, there are certain points to keep in mind with respect to Primary Keys as well.
An @Embedded field cannot contain Primary Key.
If sub fields of an embedded field has PrimaryKey annotation, they will not be considered as primary keys in the owner Entity .
If multiple primary keys are to be defined, then a list of Primary Key column names may be defined as:
However, in this case, the Primary Key may not be set as auto-generated.
If a Primary Key needs to be set as auto-generated, a single Primary Key may be used and defined as auto-generated in the @Entity data class.
There is one more option.
If PrimaryKey annotation is used on an Embedded field, all columns inherited from that embedded field becomes the composite primary key (including its grand children fields).
More annotations for Room database are here.
Источник
Room Guide
Room is Google’s new persistence library designed to make it easier to build offline apps. It tries to expose APIs that can leverage the full power of SQL while still providing an abstraction layer for managing the data as Java objects. It also works well seamlessly with Google’s Architecture Components library for building robust high-quality production apps and can also be used along with the Paging Library for handling large data sets.
The section below describes how to setup using Room.
First, make sure you have the Google Maven repository added:
Next, within your app/build.gradle , add Room to your dependency list. We create a separate variable to store the version number to make it easier to change later:
When compiling the code, the schemas for the table will be stored in a schemas/ directory assuming this statement has been included your app/build.gradle file. These schemas should be checked into your code base.
Annotate your models with @Entity :
Next, we need to add annotations for each of the fields within our model class that will map to columns in the database table:
NOTE: You must define at least one column to be the primary key. You can also define composite primary keys by reviewing this section. Use the autoGenerate=true annotation to make the column auto-increment. (Note that if you add this later, you need to update the schema of the database.)
Room allows foreign keys to be defined between objects, but explicitly disallows traversing these relations automatically. For instance, trying to lookup an Organization object in User object in memory cannot be done. Defining the constraints between these two tables however can still be done:
We can declare the types of SQL queries we want to perform in our data access object (DAO) layer. The following annotations can be used to get, insert, and delete a User object:
Create a MyDatabase.java file and annotate your class with the @Database decorator to declare your database. It should contain both the name to be used for creating the table, as well as the version number.
Note: if you decide to change the schema for any tables you create later, you will need to bump the version number. The version number should always be incremented (and never downgraded) to avoid conflicts with older database versions. Making schema changes will update the definitions in the app/schemas directory as setup in the previous step. You will find the actual SQL definitions to create these tables there.
Next, we need to instantiate Room in a custom application class. If you do not have an Application object, create one in MyApplication.java as shown below:
Modify your AndroidManifest.xml file to reference this Application object for the android:name property:
There are a few key aspects of Room to note that differ slightly from traditional object relational mapping (ORM) frameworks:
Usually, ORM frameworks expose a limited subset of queries. Tables are declared as Java objects, and the relations between these tables dictate the types of queries that can be performed. In Room, SQL inserts, updates, deletes, and complex joins are declared as Data Access Objects (DAO). The data returned from these queries simply are mapped to a Java object that will hold this information in memory. In this way, no assumptions are made about how the data can be accessed. The table definitions are separate from the actual queries performed.
ORMs typically handle one-to-one and one-to-many relationships by determining the relationship between tables and expose an interface or a set of APIs that perform the SQL queries behind the scenes. Because of the performance implications, Room requires handling these relationships explicitly. The query needs to be defined as DAO objects, and the data returned will need to be mapped to Java objects.
One additional change is that Room doesn’t require defining your SQL tables as a single Java object. It allows you to maintain encapsulation between objects by allowing other Java objects to be included as part of a single table.
Basic creation, read, update, and delete (CRUD) statements need to be defined in your data access object (DAO). One major difference is that queries cannot be done on the main thread. In addition, the objects can be inserted with an AsyncTask or using the runInTransaction() method :
All queries must be defined in the Data Access Object (DAO):
If we want to perform a User query that also retrieves the organization info into memory, we can do the following:
Notice how the organization name has been renamed using the SQL AS query. This helps to sidestep naming conflicts since both the User and Organization both share the same field name. We can then need to declare a regular Java object that includes both a User and the organization name:
You can then access the properties of User within this object as we would as a normal Java object:
Suppose you wanted to query all the columns in the Organization table and have this information stored in this object. The problem of course is that the User and Organization both have an id column and conflict. To get around this issue, the prefix parameter can be used with the @Embedded notation:
Our data access object would then be the following:
We can then perform this query through an AsyncTask:
Updating rows require defining an @Insert annotation. The primary key will be returned if the return type is declared as a Long type:
Multiple primary keys can also be returned if the DAO includes multiple parameters:
Remember again that you need to dispatch the task in a separate thread:
Deleting requires either a class annotated as an Entity or a collection:
You can leverage the runInTransaction() method that comes with Room:
Question: How do I inspect the SQLite data stored on the device?
In order to inspect the persisted data, we need to use adb to query or download the data. You can also take a look at using the Stetho library, which provides a way to use Chrome to inspect the local data.
Question: How does Room handle duplicate IDs? For example, I want to make sure no duplicate twitter IDs are inserted. Is there a way to specify a column is the primary key in the model?
Simply annotate the post ID column as the @PrimaryKey annotation:
Make sure to uninstall the app afterward on the emulator to ensure the schema changes take effect. Note that you may need to manually ensure that you don’t attempt to re-create existing objects by verifying they are not already in the database as shown below.
Question: How do you specify the data type (int, text)? Does Room automatically know what the column type should be?
The type is inferred automatically from the type of the field.
Question: How do I store dates into Room?
Room supports type converters:
Then you would define a DataConverter class similar to the following:
Question: Is it possible to do joins with Room?
You must define the queries directly as Data Access Objects (DAO). The return type defined in these interfaces must match the data returned. See the example above in the querying rows section.
There are a few key aspects of Room to note that differ slightly from traditional object relational mapping (ORM) frameworks:
Usually, ORM frameworks expose a limited subset of queries. Tables are declared as Java objects, and the relations between these tables dictate the types of queries that can be performed. In Room, SQL inserts, updates, deletes, and complex joins are declared as Data Access Objects (DAO). The data returned from these queries simply are mapped to a Java object that will hold this information in memory. In this way, no assumptions are made about how the data can be accessed. The table definitions are separate from the actual queries performed.
ORMs typically handle one-to-one and one-to-many relationships by determining the relationship between tables and expose an interface or a set of APIs that perform the SQL queries behind the scenes. Because of the performance implications, Room requires handling these relationships explicitly. The query needs to be defined as DAO objects, and the data returned will need to be mapped to Java objects.
One additional change is that Room doesn’t require defining your SQL tables as a single Java object. It allows you to maintain encapsulation between objects by allowing other Java objects to be included as part of a single table.
Источник