In the room documentation there is this example of how to use #Relation to get all 'Users' and all 'Pets'
public class User {
int id;
// other fields
}
public class PetNameAndId {
int id;
String name;
}
public class UserAllPets {
#Embedded
public User user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
public List pets;
}
#Dao
public interface UserPetDao {
#Query("SELECT * from User WHERE age > :minAge")
public List<UserAllPets> loadUserAndPets(int minAge);
}
However, if instead of wanting to get all Pets, in this scenario, I just wanted all Users and most recent Pet created, A group by userid.
Would I just filter the results after querying the database, or would it be better to create a foreign key query.
Related
Using the #Relation annotation. I can query a one to many relationship using the following:
#Dao
public interface PostDao {
#Query("SELECT * FROM post")
List<PostWithComments> getPostWithComments();
}
Here are the entities
#Entity
public class Post {
#PrimrayKey
private int id;
private String title;
private String content;
}
#Entity
public class Comment {
#PrimrayKey
private int id;
private int post_id;
private String content;
private String status;
}
public class PostWithComments {
#Embedded
public Post post;
#Relation(parentColumn = "id", entityColumn = "post_id", entity = Comment.class)
public List<Comment> comments;
}
I would like to get all posts that have a comment with status = approved but I'm not exactly sure how room handles this. I tried the following:
#Dao
public interface PostDao {
#Query("SELECT * FROM post INNER JOIN comment ON post.id = comment.post_id WHERE comment.status = 'approved'")
List<PostWithComments> getPostWithComments();
}
I got duplicates in the results. Each post is there multiple times in List<PostWithComments> results.
Update:
After reading the generated code at PostDao_Impl.java it seems that Room is doing a sub query to fetch the relation.
First, it executes the query in the #Query annotation from the getPostWithComments method, and then it generates a sub query for the relation to populate List<Comment>
SELECT id, post_id, title, content FROM comment WHERE post_id IN ( and some other logic, and there doesn't seem to be a way to modify the generated sub query.
Is there another way to do this?
With #Relation, you can use #DatabaseView
#DatabaseView("SELECT * FROM comments WHERE status = 'approved'")
public class ApprovedComment {
#Embedded
Comment comment;
}
PostWithComments class
public class PostWithComments {
#Embedded
public Post post;
#Relation(parentColumn = "id", entityColumn = "post_id", entity = ApprovedComment.class)
public List<ApprovedComment> comments;
}
DAO
#Dao
public interface PostWithCommentsDao {
#Query("SELECT * FROM post")
List<PostWithComments> getPostWithComments();
}
You also need to update your Database class that extends RoomDatabase and you may need to update the version.
#Database(entities = {Post.class, Comment.class}, views = {ApprovedComment.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase
Not tested but you can try this...
public class PostWithComments {
#Embedded
public Post post;
#Embedded
public Comment comment;
}
#Dao
public interface PostWithCommentsDao {
#Query("SELECT post.*, comment.* FROM post LEFT JOIN comment ON post.id=comment.post_id where comment.status = 'approved'")
List<PostWithComments> getPostWithComments();
}
My Question is similar with Android Room: Insert relation entities using Room
but with little changes
#Entity
public class Pet {
#PrimaryKey
public int id; // Pet id
public int userId; // User id
public String name;
//Added EXTRA column
public long time;// pet since have
}
Currently UserWithPets POJO is
// Note: No annotation required at this class definition.
public class UserWithPets {
#Embedded
public User user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
public List<Pet> pets;
}
And Query is
#Query("SELECT * FROM User")
public List<UserWithPets> loadUsersWithPets();
But I need something like that
UserWithLatestPet POJO:
public class UserWithLatestPet {
#Embedded
public User user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
public Pet latestPet;//Retrieve only latest (since time) pet per user
}
//Query
#Query("SELECT * FROM User")
public List<UserWithLatestPet> loadUserWithLatestPet();
What will be best way to query with only latest pet from Pet Entity along with User
First, you can define a single Pet as the result of the Relation only since Room Version 2.2.0-alpha01
I think you can do 2 things:
1. Create a query to left join the needed pet
2. Create a correct relation List<Pet> and then scan the List of the latest one.
In case you want to save a little you can create a POJO that holds only the time and id of the List of pets so you only get the minimal data you need, scan the List for the latest one and then query for that Pet.
I'm using ROOM for my project. But I'm having trouble for joining between tables, because the tables have a same name fields.
for example, my project has tree tables, "word" & "favorite" & "bookmark", which has a same name fields.
1- word
#Entity(tableName = "word")
public class Word {
#NonNull
#PrimaryKey(autoGenerate = true)
private int id;
#NonNull
private String title;
private String mean;
private String pronunciation;
// Getter and Setter
}
2- favorite
#Entity(tableName = "favorite",
foreignKeys = #ForeignKey(
entity = Word.class,
parentColumns = "id",
childColumns = "word_id",
onDelete = CASCADE,
onUpdate = CASCADE))
public class Favorite {
#NonNull
#PrimaryKey
private int word_id;
private long time;
// Getter and Setter
}
3- bookmark
#Entity(tableName = "bookmark",
primaryKeys = {"word_id", "category_id"},
foreignKeys = {
#ForeignKey(entity = Word.class,
parentColumns = "id",
childColumns = "word_id",
onDelete = CASCADE,
onUpdate = CASCADE)})
public class Bookmark {
#NonNull
private int word_id;
private long time;
private int color;
// Getter and Setter
}
To create a join between the three, I defined a new type called "WordAndFavoriteAndBookmark", and used "#Embedded" (as shown below).
and to fix the same field problem, I used prefix as a Thomas Fischer response
4- WordAndFavoriteAndBookmark
public class WordAndFavoriteAndBookmark {
#Embedded
private Word word;
#Embedded(prefix = "favorite_")
private Favorite favorite;
#Embedded(prefix = "bookmark_")
private Bookmark bookmark;
//Getter and Setter
public Word getWord() { return word; }
public void setWord(Word word) {this.word = word;}
public Favorite getFavorite() { return favorite; }
public void setFavorite(Favorite favorite) { this.favorite = favorite;}
public Bookmark getBookmark() { return bookmark; }
public void setBookmark(Bookmark bookmark) { this.bookmark = bookmark; }
}
To create a join between these tables I have defined a new #Dao.
#Dao
public interface WordAndFavoriteAndBookmarkDao {
#Query("SELECT word.*, favorite.time FROM word LEFT JOIN favorite " +
"ON word.id = favorite.word_id")
LiveData<List<WordAndFavoriteAndBookmark>> getAllWordsByFavoritesForLanguage();
}
But again, after using the query, I encounter the error for the following code in my view(Activity or Fragment):
mWordAndFavoriteAndBookmark.getFavorite().getTime();
I think this is due to the use of Perfix, but I do not know the solution to this problem
EDITED: by Thomas Fisher answer, everything is fine. But when used "Count" or "Sum" in my query, I have problem for reading these values and I don't know how to read them.
You don't need a separate dao.
Except for one plain, "#Embedded" annotation, the other annotations have to set a prefix: #Embedded(prefix = "favorite_") and #Embedded(prefix = "bookmark_")
In your query, your selected fields have to have the prefix.
Instead of
select * from word join bookmark join favorite // pseudo code
You have to alias the fields to your prefix:
select word.*, bookmark.id as bookmark_id..., favorite.id as favorite_id... from...
This prevents the three id columns to override each other in the query. And with the prefix, the dao will expect all columns of that table to have that prefix in front of the actual column name.
Also what exact error do you get?
I'd guess there might be a NPE. That might have to do with your query. So if you can include the query as well, it would help find the issue.
To solve the "Count" or "Sum" field problem, I used the new compound data class.
As below:
public static class CategoryAndEssentials {
#Embedded
private BookmarkCategory bookmarkCategory;
private int wordCount;
public BookmarkCategory getBookmarkCategory() { return bookmarkCategory; }
public void setBookmarkCategory(BookmarkCategory bookmarkCategory) { this.bookmarkCategory = bookmarkCategory;}
public int getWordCount() { return wordCount; }
public void setWordCount(int wordCount) { this.wordCount = wordCount; }
}
And in the query definition, in Dao class, I referred to this field:
#Query("SELECT " +
"bookmarkCategory.*, " +
"COUNT(bookmark.category_id) AS wordCount, " +
"FROM bookmarkCategory LEFT JOIN bookmark " +
"ON bookmarkCategory.id = bookmark.category_id " +
"GROUP BY bookmarkCategory.id ")
LiveData<List<EntityClassForJoinTables.CategoryAndEssentials>> getAllCategoriesAndEssentials();
Note that the alias column in query must have a same as the variable defined in the compound data class
I'm testing Room persistence library. I have two entity classes:
#Entity
public class User {
#PrimaryKey
int id;
String name;
}
#Entity(foreignKeys = #ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "userId")
public class Pet {
#PrimaryKey
int id;
String name;
int userId;
}
Now I'd like to get list of Pet and in each Pet object keep actual reference to User according to his userId. So each time when the userId is changed, this reference also should be changed. Is it possible to do? Or maybe there is a better way to handle relation like this?
Actually, ROOM not recommended to do that. The reasons are in the reference link.
Maybe you could try it,
#Entity
public class User {
#PrimaryKey
int id;
String name;
#Embedded
public List<Pet> userPet;
}
public class Pet {
int id;
String name;
}
Understand why Room doesn't allow object references
A convenience annotation which can be used in a Pojo to automatically fetch relation entities.
I've added one to many relationship in Room using Relation.
I referred to this post to write the following code for relation in Room.
The post tells how to read the values from the database but storing the entities into the database resulted in userId to be empty which means there is no relation between the 2 tables.
I'm not sure what is the ideal way to insert a User and List of Pet into the database while having userId value.
1) User Entity:
#Entity
public class User {
#PrimaryKey
public int id; // User id
}
2) Pet Entity:
#Entity
public class Pet {
#PrimaryKey
public int id; // Pet id
public int userId; // User id
public String name;
}
3) UserWithPets POJO:
// Note: No annotation required at this class definition.
public class UserWithPets {
#Embedded
public User user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
public List<Pet> pets;
}
Now to fetch the records from DB we use the following DAO:
#Dao
public interface UserDao {
#Insert
fun insertUser(user: User)
#Query("SELECT * FROM User")
public List<UserWithPets> loadUsersWithPets();
}
EDIT
I have created this issue https://issuetracker.google.com/issues/62848977 on the issue tracker. Hopefully they will do something regarding it.
You can do this by changing your Dao from an interface to an abstract class.
#Dao
public abstract class UserDao {
public void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
_insertAll(pets);
}
#Insert
abstract void _insertAll(List<Pet> pets); //this could go in a PetDao instead...
#Insert
public abstract void insertUser(User user);
#Query("SELECT * FROM User")
abstract List<UserWithPets> loadUsersWithPets();
}
You can also go further by having a User object have an #Ignored List<Pet> pets
#Entity
public class User {
#PrimaryKey
public int id; // User id
#Ignored
public List<Pet> pets
}
and then the Dao can map UserWithPets to User:
public List<User> getUsers() {
List<UserWithPets> usersWithPets = loadUserWithPets();
List<User> users = new ArrayList<User>(usersWithPets.size())
for(UserWithPets userWithPets: usersWithPets) {
userWithPets.user.pets = userWithPets.pets;
users.add(userWithPets.user);
}
return users;
}
This leaves you with the full Dao:
#Dao
public abstract class UserDao {
public void insertAll(List<User> users) {
for(User user:users) {
if(user.pets != null) {
insertPetsForUser(user, user.pets);
}
}
_insertAll(users);
}
private void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
_insertAll(pets);
}
public List<User> getUsersWithPetsEagerlyLoaded() {
List<UserWithPets> usersWithPets = _loadUsersWithPets();
List<User> users = new ArrayList<User>(usersWithPets.size())
for(UserWithPets userWithPets: usersWithPets) {
userWithPets.user.pets = userWithPets.pets;
users.add(userWithPets.user);
}
return users;
}
//package private methods so that wrapper methods are used, Room allows for this, but not private methods, hence the underscores to put people off using them :)
#Insert
abstract void _insertAll(List<Pet> pets);
#Insert
abstract void _insertAll(List<User> users);
#Query("SELECT * FROM User")
abstract List<UserWithPets> _loadUsersWithPets();
}
You may want to have the insertAll(List<Pet>) and insertPetsForUser(User, List<Pet>) methods in a PetDAO instead... how you partition your DAOs is up to you! :)
Anyway, it's just another option. Wrapping your DAOs in DataSource objects also works.
There is no native solution till any update in Room Library but you can do this by a trick. Find below mentioned.
Just Create a User with Pets (Ignore pets). Add getter and setter. Notice that we have to set our Id's manually later and can't use autogenerate.
#Entity
public class User {
#PrimaryKey
public int id;
#Ignore
private List<Pet> petList;
}
Create a Pet.
#Entity
public class Pet
{
#PrimaryKey
public int id;
public int userId;
public String name;
}
The UserDao should be an abstract class instead of an Interface. Then finally in your UserDao.
#Insert
public abstract void insertUser(User user);
#Insert
public abstract void insertPetList(List<Pet> pets);
#Query("SELECT * FROM User WHERE id =:id")
public abstract User getUser(int id);
#Query("SELECT * FROM Pet WHERE userId =:userId")
public abstract List<Pet> getPetList(int userId);
public void insertUserWithPet(User user) {
List<Pet> pets = user.getPetList();
for (int i = 0; i < pets.size(); i++) {
pets.get(i).setUserId(user.getId());
}
insertPetList(pets);
insertUser(user);
}
public User getUserWithPets(int id) {
User user = getUser(id);
List<Pet> pets = getPetList(id);
user.setPetList(pets);
return user;
}
Your problem can be solved by this without creating UserWithPets POJO.
As Room does not manage the Relations of the entities, you have to set the userId on each pet yourself and save them. As long as there are not too many pets at once, I'd use an insertAll method to keep it short.
#Dao
public interface PetDao {
#Insert
void insertAll(List<Pet> pets);
}
I don't think there's any better way at the moment.
To make the handling easier, I'd use an abstraction in the layer above the DAOs:
public void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
petDao.insertAll(pets);
}
Currently there is no native solution to this problem. I have created this https://issuetracker.google.com/issues/62848977 on Google's issue tracker and the Architecture Components Team said they will adding a native solution in or after v1.0 of Room library.
Temporary Workaround:
Meanwhile you can use the solution mentioned by tknell.
public void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
petDao.insertAll(pets);
}
I managed to insert it properly with a relatively simple workaround. Here are my entities:
#Entity
public class Recipe {
#PrimaryKey(autoGenerate = true)
public long id;
public String name;
public String description;
public String imageUrl;
public int addedOn;
}
#Entity
public class Ingredient {
#PrimaryKey(autoGenerate = true)
public long id;
public long recipeId;
public String name;
public String quantity;
}
public class RecipeWithIngredients {
#Embedded
public Recipe recipe;
#Relation(parentColumn = "id",entityColumn = "recipeId",entity = Ingredient.class)
public List<Ingredient> ingredients;
I am using autoGenerate for auto-increment value(long is used with a purpoes).
Here is my solution:
#Dao
public abstract class RecipeDao {
public void insert(RecipeWithIngredients recipeWithIngredients){
long id=insertRecipe(recipeWithIngredients.getRecipe());
recipeWithIngredients.getIngredients().forEach(i->i.setRecipeId(id));
insertAll(recipeWithIngredients.getIngredients());
}
public void delete(RecipeWithIngredients recipeWithIngredients){
delete(recipeWithIngredients.getRecipe(),recipeWithIngredients.getIngredients());
}
#Insert
abstract void insertAll(List<Ingredient> ingredients);
#Insert
abstract long insertRecipe(Recipe recipe); //return type is the key here.
#Transaction
#Delete
abstract void delete(Recipe recipe,List<Ingredient> ingredients);
#Transaction
#Query("SELECT * FROM Recipe")
public abstract List<RecipeWithIngredients> loadAll();
}
I had problem linking the entities, auto generate produced "recipeId=0" all the time. Inserting the recipe entity firstly fixed it for me.
Now at v2.1.0 Room seems to be not suitable for models with nested relations. It needed lots of boilerplate code to maintain them. E.g. manual insert of lists, creating and mapping local IDs.
This relations-mapping operations are done out of box by Requery https://github.com/requery/requery Additionaly it does not have issues with inserting Enums and have some converters for other complex types like URI.