- Room: Один ко многим
- Один ко многим
- Целостность
- Сохранение
- Live Data
- Database relations with Room
- One-to-one relations
- One-to-many relations
- Many-to-many relations
- Advanced relation use cases
- 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
- Room database with one-to-one relation
- 3 Answers 3
Room: Один ко многим
Один ко многим
Тут все просто: в документации сказано как организовать классы, чтобы получить связь сущностей «один ко многим». Берем 2 сущности:
и связываем их в единую сущность:
Для получения новой сущности типа DialogWithTags нужно использовать Dao, которое будет загружать данные из таблицы DialogPojo, при этом автоматически загрузятся tags из связанной таблицы (entity = TagPojo.class):
Используя эти знания уже можно собирать звезду смерти приложение. Однако, в процессе работы могут возникнуть вопросы, ответы на которые лучше знать на подготовительных этапах.
Целостность
Как это ни странно, но запрос на получение DialogWithTags не гарантирует целостность данных. Т.е., возможна ситуация, когда DialogPojo уже загружен, а список TagPojo нет. Предупреждение о возможных проблемах появляется на этапе компиляции программы.А кто их читает, эти предупреждения? Для обеспечения целостности данных, в запрос нужно добавить аннотацию Transaction.
Сохранение
К сожалению, сохранить модель DialogWithTags просто так не получится. Сохранять данные нужно отдельно и, желательно, в одной транзакции, например:
Live Data
Самое большое разочарование ждет при использовании LiveData. Данные будут живыми только для Embedded поля dialog. Изменения для tags отслеживаться не будут. Конечно можно объявить поле tags как LiveData, но, не стоит забывать, что LiveData вернет данные только в том случае, если зарегистрирован хотя бы один обсервер.
Источник
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:
Источник
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 👏!
Источник
Room database with one-to-one relation
I have 2 Entities, Coin and CoinRevenue.
Basically, coin holds the price in USD for some other currency.
For example, Coin with symbol EUR with value of 1.0356
CoinRevenue is an Entity that I use to hold how much coins of that specific coins the User have. For example, CoinRevenue has relation to Coin Entity with EUR symbol and amount of 1000.
Now I want to fetch CoinRevenue from the database and get the updated Coin from the database.
for example, i saved the Coin with (EUR,1.0253) and than Saved a CoinRevenue with that coin.
After that I updated the Coin with (EUR,2.522) I want that the Coin object inside CoinRevenue will be updated as well.
I understand that @Embedded just add the inner objet fields as colums to the same parent object. and when I use relation, I have to use a List or a Set. but I always have 1 Coin inside CoinRevenue.
What is the best way to creat this?
3 Answers 3
So after a lot of tries, I’ve managed to get it working.
I Changed the CoinRevenue object to hold a foreign key to the Coin id
I needed to create a POJO with both objects, like that:
and the query it like this:
In addition this query, as any other regular objects query, emit objects if there is any change in the ‘coin’ table or the ‘coinRevenue’ table
Your solution has several major drawback. One of them is that the tables’ columns has to have different names. Instead of using @embededed I suggest to apply @Relation.
I am not familiar with Kotlin so the solution is in Java
And Dao is simple as that
It’s a little hard to tell what you are really trying to achieve. Also, your naming is a little odd. It seems that the coin table really contains the currency information. The coinRevenueNew is a ledger entry or order. If you pick easier to follow examples, more people will try to finish reading your posts.
Also, the problem that you are trying to solve is a little unclear. — Is your problem modelling it in the database? — Is your problem that you want to have all amounts automatically updated when the currency changes? — Is your problem that you want to have the objects in memory updated when the database changes? — Is your problem with using foreign keys with Room?
The first issue with modelling has been hinted on by other people. You use a foreign key. There are plenty of articles about it.
Using a little more understandable domain names, you’d have two tables like these:
You’d create Room entities for each. You’d create a third class (do not mark it with @Entity) that combines both. You can use the annotations @Embedded or @Relation here. The documentation explains this further:
If you change the currency, the storage system will not automatically update all the order totals. If you have a field of «total_in_foreign_currency» and a field of «total_in_master_currency», the database will not recalculate for you. You have to manually iterate over each row and recalculate it.
In memory data objects won’t magically update. You have to keep track of when you retrieved the data and if it is still now. You can use LiveData to be notified whenever the data changes (but it won’t magically update your objects).
Источник