I new to Room, and #Relation isn't clear for me.
If I understand correctly, I have entities e.g. (RSS)ChannelEntity, and channel has items named ItemEntity.
These are classes wiht #Entity annotation.
I also have a POJO to "connect" my entites. I mean I've to write a POJO like this:
public class Channel {
#Embedded
private ChannelEntity channel;
// Link is the primary key in ChannelyEntity
#Relation (parentColumn = "link", entityColumn = "channel_link")
private ArrayList<ItemEntity> items;
// Getters and Setters are here
}
Than I've to write a dao interface where I can get Channel (not ChannelEntity) like this:
public interface ChannelDao {
#Query("SELECT * FROM channels WHERE link = :link LIMIT 1")
Channel getChannelById(String link);
#Query("SELECT * FROM channels")
ArrayList<Channel> getAllChannels();
}
With these Entities, DAOs and POJOs I can get Channel objects which contain a list of Items with the corresponding link (id). Is that right?
My other is question about the rest CRUD. E.g. if I'd like to save a new channel can I add this statement to my ChannelDao?
#Insert(onConflict = OnConflictStrategy.REPLACE)
void createChannels(Channel... channels);
to delete
#Delete
void deleteChannels(Channel... channels);
and so on. So will it create and delete the ChannelEntities and ItemEntities from the passed Channel object?
I updated my classes as #CommonsWare adviced it.
Now I have entity classes with #Embedded object:
#Entity (tableName = "channels")
public class ChannelEntity {
// Required channel elements
// The name of the channel. It's how people refer to your service.
private String title;
// The URL of the HTML website corresponding to the channel
#PrimaryKey
private String link;
//other fileds
#Embedded
private TextInputEntity textInputEntity;
#Embedded
private ImageEntity imageEntity;
//getters and setters
}
One of the embedded classes:
// Specifies a text input box displayed with the channel.
// Embedded in ChannelEntity
public class TextInputEntity {
// Required elements
// The label of the Submit button in the text input area.
private String title;
// Explains the text input aera.
private String description;
// The name of the text object int hte text input area.
private String name;
// The URL of the CGI script that processes the text input request
private String link;
#ColumnInfo (name = "channel_link")
private String channelLink;
}
I've wrote daos for all classes have #Entity annotation(I called embedded classes entity even they aren't, but I'd have model classes for views and I don't want to mix up them later).
I mapped relations like this:
// All items are optional, but at least one of title or description must be presented.
#Entity (tableName = "items"
, foreignKeys = #ForeignKey (entity = Channel.class
, parentColumns = "link"
, childColumns = "channel_link"))
public class ItemEntity {
#PrimaryKey (autoGenerate = true)
private int id;
// Title of the item
private String title;
// Other fileds
#ColumnInfo (name = "channel_link")
private String channelLink;
// Getters and setters
And when I want to get a Channel with the list of items I get it like this:
public class Channel {
#Embedded
private ChannelEntity channel;
#Relation (parentColumn = "link", entityColumn = "channel_link")
private ArrayList<ItemEntity> items;
#Relation (parentColumn = "link", entityColumn = "channel_link")
private ArrayList<SkipDayEntity> skipDays;
#Relation (parentColumn = "link", entityColumn = "channel_link")
private ArrayList<SkipHourEntity> skipHours;
//Setters and getters
}
And this is the dao for Channel:
#Dao
public interface ChannelDao {
#Insert (onConflict = OnConflictStrategy.REPLACE)
void insertChannel(ChannelEntity channel);
#Update
void updateChannel(ChannelEntity channel);
#Delete
void deleteChannel(ChannelEntity channel);
#Query ("SELECT * FROM channles WHERE link = :link LIMIT 1")
Channel getChannelByLink(String link);
#Query ("SELECT * FROM channels")
LiveData<ArrayList<Channel>> getAllChannels();
}
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 am developing an Android app using the Room persistence library. I have a User and a Car entity
#Entity(tableName = "users")
public class User {
#PrimaryKey
#NonNull
private int id;
private String name;
public User(#NonNull int id, String name) {
this.id = id;
this.name = name;
}
}
and
#Entity(tableName = "cars", foreignKeys = #ForeignKey(parentColumns =
"id", childColumns = "userId", entity = User.class))
public class Car {
#PrimaryKey(autoGenerate = true)
private int id;
private int userId;
private String brand;
public Car(int userId, String brand) {
this.userId = userId;
this.brand = brand;
}
}
Also I have created a UserWithCar class as below:
public class UserWithCar {
#Embedded(prefix = "user_")
public User user;
#Embedded(prefix = "car_")
public Car car;
}
As you can see in the UserWithCar I use a prefix cause if I don't I get the following error:
Multiple fields have the same columnName: id. Field names: user > id,
car > id.
I want to get all the UserWithCar using the following query:
#Query("SELECT * FROM users JOIN cars ON users.id = cars.userId")
List<UserWithCar> getUserWithCar();
Using this query I get the following error:
The query returns some columns [id, name, id, userId, brand] which are
not use by com.roomdemo.data.models.UserWithCar. You can use
#ColumnInfo annotation on the fields to specify the mapping.
com.roomdemo.data.models.UserWithCar has some fields [user_id,
user_name, car_id, car_userId, car_brand] which are not returned by
the query. If they are not supposed to be read from the result, you
can mark them with #Ignore annotation. You can suppress this warning
by annotating the method with
#SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by
the query: id, name, id, userId, brand. Fields in
com.foodtec.roomdemo.data.models.UserWithCar: user_id, user_name,
car_id, car_userId, car_brand.
Can I have some help? Thanks!
Update
Using #Wizard help, I removed the prefix from the #Embeded and I added #ColumnInfo "uId" for the User's id and "cId for the Car's id in order to not have the same id field. By this way it works!
Columns returned by the query: id, name, id, userId, brand. Fields in
com.foodtec.roomdemo.data.models.UserWithCar: user_id, user_name,
car_id, car_userId, car_brand.
Error indicates that, columns returned by query is different from Pojo class. It should be the same. Alternatively you can map your Pojo variable to column name using #ColumnInfo annotation.
For example,
#PrimaryKey
#NonNull
#ColumnInfo(name = "user_id")
private int id;
This way, id will be mapped to user_id.
Change your query:
#Query("SELECT * FROM users JOIN cars ON users.id = cars.userId")
to specify the columns in the POJO class UserWithCar. The columns returned must match all the columns, and have the same column name as your POJO. You can use AS to change the column names in the query.
#Query("SELECT userId as userId , brand as brand FROM users JOIN cars ON users.id = cars.userId")
Alternatively, you can use #ColumnInfo to specify the column name mappings.
I am having a hard time getting a list item into room. the list item is called measurements and its of type Measurement. the list item has no primarykey that would be related to the database.
but i have no problem adding the same primary key for the ProductModel if necessary.
Here is what i have so far:
#Entity(tableName = TABLE_NAME)
public class ProductModel {
public static final String TABLE_NAME = "product";
#PrimaryKey
private int idProduct;
private int idCategoryDefault;
#Relation(parentColumn = "idProduct", entityColumn = "idProduct", entity = SortedAttribute.class)
private List<SortedAttribute> sortedAttributes = null;
}
#Entity
public class SortedAttribute {
#PrimaryKey
private int idProduct;
private String reference;
#Embedded
private List<Measurement> measurements = null; //****how do i get this into room ? its a LIST of measurements, not a measurement so calling Embedded i think wont work as it cant flatten it****/
}
public class Measurement {
private String value;
private String valueCm;
public Measurement() {
}
}
Embedded annotation can be used on a POJO or Entity only, not for a List. So, Room can not automatically flatten your list in this case.
You can use TypeConverter to convert List<Measurement> into String(in JSON format) and vise versa. You can use any JSON parser library to support it. For example, I use Gson as following.
public class ProductTypeConverters {
#TypeConverter
public static List<Measurement> stringToMeasurements(String json) {
Gson gson = new Gson();
Type type = new TypeToken<List<Measurement>>() {}.getType();
List<Measurement> measurements = gson.fromJson(json, type);
return measurements;
}
#TypeConverter
public static String measurementsToString(List<Measurement> list) {
Gson gson = new Gson();
Type type = new TypeToken<List<Measurement>>() {}.getType();
String json = gson.toJson(list, type);
return json;
}
}
#Entity
#TypeConverters(ProductTypeConverter.class)
public class SortedAttribute {
#PrimaryKey
private int idProduct;
private String reference;
private List<Measurement> measurements = null;
}
EDIT: Use a type converter
#Relation is what you are looking for.
https://developer.android.com/reference/android/arch/persistence/room/Relation.html
From the Room docs:
#Entity
public class Pet {
# PrimaryKey
int petId;
String name;
}
public class UserNameAndAllPets {
public int userId;
public String name;
#Relation(parentColumn = "petId", entityColumn = "userId")
public List<Pet> pets;
}
#Dao
public interface UserPetDao {
#Query("SELECT petId, name from User")
public List<UserNameAndAllPets> loadUserAndPets();
}
Note: Upon further research, Room does not quite support lists of objects that are INSIDE objects. I (and others) have opted to handle the lists separately. Room can handle lists of objects just fine as long as they aren't within an object; so as long as your items inside the list are related to your overall object you can recover the list.
So, you would actually #Ignore the list and just handle it in your Dao abstract class. I could not find the SO post's I found earlier that portray this.