- Room: Один ко многим
- Один ко многим
- Целостность
- Сохранение
- Live Data
- Урок 7. Room. Insert, Update, Delete, Transaction
- Insert
- Вставка нескольких объектов
- Получение id
- Режимы вставки
- Update
- Delete
- Транзакции
- Android Architecture Components: Room — Relationships
- Android Architecture Components: Room — Introduction
- Recently Google announced a set of new libraries for designing Android application’s architecture — Android…
- Introduction
- One-to-many relation
- SQL query
- Many-to-many relation
- Using @Relation annotation
- Conclusion
- Урок 6. Room. Entity
- Имя таблицы
- Имя поля
- Тип поля
- Модификаторы доступа
- Первичный ключ
- Внешний ключ
- Индекс
- Вложенные объекты
- Ignore
Room: Один ко многим
Один ко многим
Тут все просто: в документации сказано как организовать классы, чтобы получить связь сущностей «один ко многим». Берем 2 сущности:
и связываем их в единую сущность:
Для получения новой сущности типа DialogWithTags нужно использовать Dao, которое будет загружать данные из таблицы DialogPojo, при этом автоматически загрузятся tags из связанной таблицы (entity = TagPojo.class):
Используя эти знания уже можно собирать звезду смерти приложение. Однако, в процессе работы могут возникнуть вопросы, ответы на которые лучше знать на подготовительных этапах.
Целостность
Как это ни странно, но запрос на получение DialogWithTags не гарантирует целостность данных. Т.е., возможна ситуация, когда DialogPojo уже загружен, а список TagPojo нет. Предупреждение о возможных проблемах появляется на этапе компиляции программы.А кто их читает, эти предупреждения? Для обеспечения целостности данных, в запрос нужно добавить аннотацию Transaction.
Сохранение
К сожалению, сохранить модель DialogWithTags просто так не получится. Сохранять данные нужно отдельно и, желательно, в одной транзакции, например:
Live Data
Самое большое разочарование ждет при использовании LiveData. Данные будут живыми только для Embedded поля dialog. Изменения для tags отслеживаться не будут. Конечно можно объявить поле tags как LiveData, но, не стоит забывать, что LiveData вернет данные только в том случае, если зарегистрирован хотя бы один обсервер.
Источник
Урок 7. Room. Insert, Update, Delete, Transaction
В этом уроке рассмотрим подробнее аннотации Insert, Update и Delete. А также узнаем, как использовать транзакции в Room.
Полный список уроков курса:
Insert
Аннотация Insert — это простой способ вставить объект в базу данных. Мы уже использовали ее в примерах прошлых уроков.
Использование этой аннотации выглядит так:
В Dao интерфейсе описываем метод, который на вход принимает Entity объект. К методу добавляем аннотацию Insert и Room сгенерирует необходимый код в реализации этого интерфейса.
Давайте посмотрим, какие еще возможности у нас есть.
Вставка нескольких объектов
Мы можем передавать в метод не один, а несколько объектов, используя varargs
Также, это может быть список:
Или это вообще может быть любой Iterable:
При вызове этого метода вы можете использовать массив или коллекцию.
Получение id
При вставке метод Insert может возвращать id только что добавленной записи. Для этого надо описать метод так, чтобы он возвращал long.
Если в Employee есть числовой первичный ключ, то именно его значение вы и получите.
В случае добавления нескольких записей, необходимо использовать long[]
Режимы вставки
Рассмотрим ситуацию, когда мы вставляем в таблицу запись, но обнаруживается, что запись с таким ключом там уже есть. По умолчанию мы получим ошибку: SQLiteConstraintException: UNIQUE constraint failed. И ничего в базу не запишется.
Но это можно поменять с помощью параметра onConflict.
В режиме REPLACE старая запись будет заменена новой. Этот режим хорошо подходит, если вам надо вставить запись, если ее еще нет в таблице или обновить запись, если она уже есть.
Также есть режим IGNORE. В этом режиме будет оставлена старая запись и операция вставки не будет выполнена.
Более подробно об этих режимах можно прочесть здесь.
Update
Эта аннотация аналогична Insert, но она не вставляет, а обновляет объекты в бд.
Так же, как и с Insert мы можем использовать коллекции и varargs, чтобы обновлять несколько объектов сразу.
Update ищет в бд запись по ключу. Если не найдет, то ничего не произойдет. Если найдет, то обновит все поля, а не только те, которые мы заполнили в Entity объекте.
Мы можем получить количество обновленных записей. Для этого опишите метод так, чтобы он возвращал int.
Как и Insert, Update поддерживает параметр onConflict.
Delete
Методы с аннотацией Delete будут удалять объекты.
В Delete методах мы также можем использовать коллекции и varargs, чтобы удалять несколько объектов сразу.
Delete ищет в бд запись по ключу.
Мы можем получить количество удаленных записей. Для этого необходимо описать метод так, чтобы он возвращал int.
Аннотации Insert, Update и Delete позволяют выполнить простые операции. Для более сложных действий необходимо использовать SQL запросы: INSERT, UPDATE и DELETE. Это можно сделать с помощью аннотации Query. В следующем уроке мы рассмотрим эту возможность.
Транзакции
Аннотация @Transaction позволяет выполнять несколько методов в рамках одной транзакции.
Рассмотрим пример, когда нам нужно добавить объекты Car и Employee:
EmployeeCarDao — отдельный Dao объект для работы с комбинацией Car и Employee. В нем описываем методы для вставки объектов по отдельности, а затем оба этих метода вызываем в одном методе с аннотацией Transaction. В итоге вставятся либо оба объекта, либо, в случае возникновения ошибки, ни один из них.
Обратите внимание, что в этом случае Dao — не интерфейс, а абстрактный класс.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Android Architecture Components: Room — Relationships
Nov 1, 2017 · 7 min read
In the previous article I wrote introduction to the Room persistence library from Android Architecture Components:
Android Architecture Components: Room — Introduction
Recently Google announced a set of new libraries for designing Android application’s architecture — Android…
Today I’d like to introduce you into creating relationships in database with Room. Let’s start!
Introduction
In SQLite databases we’re allowed to specify relationships between objects, so we can bind one or many objects with one or many others objects. It’s called one-to-many and many-to-many relationships.
For example, we can have user in databa s e. Single user can have many repositories. This is one (user) to many (repositories) relation. But on the other hand, every repository can have also it’s own users-contributors, so for every user we can have many repositories, and every repository can have many users. In this case this is called many-to-many relation.
One-to-many relation
Let’s use the example from the previous post and assume that we have User having many Repositories. In this scenario, we’ll have entity for User and Repo. Firstly, we need to create connection between User and Repo, and then we’ll be able to get proper data from the database.
In the first step, we need to create single entity for the User:
If you’re not sure what @Entity and @PrimaryKey annotations are for, you can check my previous post.
For Repository model, we’ll reuse the Repo class from the previous post with a single, but significant, change:
If you compare this Repo model with the one from the previous post, you’ll notice two differences:
- foreignKeys parameter in the @Entity annotation
- additional field for userId
Adding foreignKeys means we create connection between this entity and some other class. In this parameter we declare parentColumns, which is name of the id column from User class and childColumns, which is the name of the user id column in Repo class.
Creating this connection is not necessary for having relation, but helps you to define what should happen with the Repo row in database in case of User row delete or update. That’s what is the last parameter for: onDelete = CASCADE . We tell specifically that if user row will be deleted, we’d like to delete also all of it repositories. You can also define similar solution with onUpdate = CASCADE parameter. You can read about other possible solutions in ForeignKey documentation.
Now, after we have our models prepared, we need to create proper SQL query to select repositories for specific user.
SQL query
DAOs are the best place to create our SQL statements. Our RepoDao may look like this:
From the top, we have three methods to insert, update and delete Repo (you can read more about them in my previous post) and two methods for getting data — first one for getting all repositories, and the second for what we actually want — getting repositories for the specific user.
We’ll also need complementary UserDao with methods for insert, update and remove:
Our database class, RepoDatabase, also needs to be updated with proper model classes in @Database annotation and additional abstract method for getting UserDao:
And that’s it! Now we can use database to insert users and repositories:
Many-to-many relation
In SQL many-to-many (or M:N) relation requires having a join table with foreign keys back to the other entities. We can change example from the previous section, so now not only every user can have many repositories, but also every repository can belong to many users!
To do this, we’ll go back to the simplest versions of our models:
And the same for Repo model:
In the next step we’ll create our join table — UserRepoJoin class
At first glance it may look horrible, but give it a second chance 🙏
What do we have here?
- tableName parameter for giving our table some specific name
- primaryKeys parameter for having many primary keys — in SQL we can have not only a single primary key, but also set of primary keys! It’s called composite primary key and it’s used for declaring that every row in our join table should be unique for every pair of userId and repoId
- foreignKey parameter is for declaring an array of foreign keys to the other tables. Here we say that userId from our join table is the child id for User class and similarly for Repo model
Now, when we declared foreign keys, we’re ready to prepare SQL statement for inner join:
This way we can get both users for repository, and repositories for user. The last step is to change our RepoDatabase:
And now we can insert users and repositories into database:
Using @Relation annotation
There’s also another way of providing relationships using Room — with a @Relation annotation. You can declare such a relation only inside non-entity class. Let’s look at the example:
Above we have simply our model classes — User and Repo with userId field. Now we need to create our non-entity model class:
Here we have two new annotations:
- @Embedded is for having nested fields — this way we’ll have our User class embedded into our UserWithRepos class
- @Relation is for having relation with other model class. Those two parameters say that parentColumn name from User class is id and entityColumn name from Repo class is userId.
This way we can use proper SQL statement in our DAO to select users with all their repositories:
This is the easiest way, however, you can’t set action upon deleting or updating parents as it was with @ForeignKey annotation.
Conclusion
That’s all! I hope after this post you gained some valuable knowledge about creating object relationships in Room. If you have any suggestion, feel free to comment.
And if you really liked this post and want to make me feel happy, don’t forget to 👏!
Источник
Урок 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник